2020-07-05

作者: 恍然如梦_b700 | 来源:发表于2020-07-10 18:23 被阅读0次
    • 1. copy和strong的区别 为什么不可变对象要用copy
      strong此特质标明该属性定义了一种拥有关系。为这种属性设定新值时,设置方法会先保留新值再释放旧值,然后再讲新值设置上去。
      copy 设置方法并不保留新值而是将其拷贝
      当属性类型为NSString*时,经常用此特性来保护其封装性,因为传递给设置方法的新值有可能指向一个NSMutableString类的实例这个类是NSString的子类,表示一种可以修改其值得字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以这时就要拷贝一份不可变的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是可变的,就应该在设执行属性是拷贝一份。

    • 2.为什么block要使用copy而不是strong或者其他属性修饰?
      block本身是像对象一样可以retain,和release。但是,block在创建的时候,它的内存是分配在栈上的,而不是在堆上。他本身的作于域是属于创建时候的作用域,一旦在创建时候的作用域外面调用block将导致程序崩溃。因为栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃,在对block进行copy后,block存放在堆区.
      使用retain也可以,但是block的retain行为默认是用copy的行为实现的,
      因为block变量默认是声明为栈变量的,为了能够在block的声明域外使用,所以要把block拷贝(copy)到堆,所以说为了block属性声明和实际的操作一致,最好声明为copy。

    • 3.深拷贝和浅拷贝
      浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享一块内存
      深拷贝指针和对象的拷贝,新旧对象不会共享一块内存

    • 4.有哪些锁

    • 5.weak如何实现自动赋nil

    • 6.GET和POST的区别
      GET和POST本质上就是TCP链接,并无差别。但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。
      HTTP是什么?HTTP是基于TCP/IP的关于数据如何在万维网中如何通信的协议。

    • 7.iOS中nil, Nil, NULL和NSNull的区别

    1. nil:对象为空
    2. Nil:类为空
    3. NULL:基本数据对象指针为空
    4. NSNull:集合对象无法包含 nil 作为其具体值,如NSArray、NSSet和NSDictionary。相应地,nil值用一个特定的对象 NSNull 来表示。NSNull 提供了一个单一实例用于表示集合对象属性中的的nil值。
    • 8、手机适配一些方案

    • 9、真机调试、项目上线注意事项

    • 10.静态方法是否能被重写
      静态方法其实就是方法 可以被重写

    • 11.向一个nill对象发送消息会发生什么?

    • 12.iOS 类(class)和结构体(struct)有什么区别?
      Swift 中,类是引用类型,结构体是值类型。值类型在传递和赋值时将进行复制,而引用类型则只会使用引用对象的一个"指向"。所以他们两者之间的区别就是两个类型的区别。

    • 13. KVO 的底层实现原理
      (1)KVO 是基于 runtime 机制实现的
      (2)当一个对象(假设是person对象,对应的类为 JLperson)的属性值age发生改变时,系统会自动生成一个继承自JLperson的类NSKVONotifying_JLPerson,在这个类的 setAge 方法里面调用
      [super setAge:age];
      [self willChangeValueForKey:@"age"];
      [self didChangeValueForKey:@"age"];
      三个方法,而后面两个方法内部会主动调用
      -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context方法,在该方法中可以拿到属性改变前后的值.
      KVO的作用
      作用:能够监听某个对象属性值的改变

    • 14. 死锁
      就是队列任务的循环等待。
      四个条件
      互斥条件:一个资源只能被一个线程使用.在一段时间内,资源已被占有.如果此时有请求获取该资源,则该请求只能等待.
      请求与保持条件:线程已保持了至少一个资源,但又从新提出获取新的资源请求,而该资源已被其他线程占有.此时该请求将被阻塞等待,对于已获得的资源保持不放.
      不可剥夺条件:线程获得资源在未使用完成之前,不能被其他线程强行剥夺,只能该线程自己放弃.
      循环等待条件:若干线程形成首尾相接循环等待资源的关系.

    • 15.isKindOfClass和isMemberOfClass的区别
      isKindOfClass:确定一个对象是否是一个类的成员,或者是该类的子类成员
      isMemberOfClass:只能确定一个对象是否是当前类的成员.

    • 16 runtime的用途
      1.做用户埋点统计
      2 处理异常崩溃(NSDictionary, NSMutableDictionary, NSArray, NSMutableArray 的处理)
      3 按钮最小点击区设置
      4 按钮重复点击设置
      5 手势的重复点击处理
      6 UIButton点击事件带多参数
      7 MJRefresh封装
      8 服务端控制页面跳转
      9 字典转模型

    • 17 说一下响应链的原理
      当用户点击屏幕后,UIApplication 先响应事件,然后传递给UIWindow。如果window可以响应。就开始遍历window的subviews。遍历的过程中,如果第一个遍历的view1可以响应,那就遍历这个view1的subviews(依次这样不停地查找,直至查找到合适的响应事件view)。如果view1不可以响应,那就开始对view2进行判断和子视图的遍历。依次类推view3,view4…… 如果最后没有找到合适的响应view,这个消息就会被抛弃。(整个遍历的过程就是树的先序遍历)
      通过上面的 hitTest:withEvent: 寻找到第一响应者后,需要逆着寻找第一响应者的方向(从第一响应者->UIApplication)来响应事件。

    • 18. hitTest有尝试过重写吗?
      重写过,在用runtime给按钮扩大点击区域的分类里面,重写了hittest,在方法里面判断点击区域和自己设置的是否相等,如果相等,返回self,让自己成为第一响应者

    19.iOS中内存管理?
    iOS中内存管理的方式主要有三大,1.taggedPointer,2.NONPOINTER_ISA,3.散列表。

    20. KVO
    KVO的实现用了一种叫 isa-swizzling 的技术。
    当一个对象的一个属性注册了观察者后,被观察对象的isa指针的就指向了一个系统为我们生成的中间类NSKVONotifying_,而不是我们自己创建的类。在这个类中,系统为我们重写了被观察属性的setter方法。
    通过 object_getClass(id obj) 方法可以获得实例对象真实的类(isa指针的指向)。
    可以看到 _NSSetObjectValueAndNotify 还是调用了 willChangeValueForKey: 和 didChangeValueForKey: 来进行手动通知的。

    21.UIView & CALayer的区别
    联系
    每一个 view 中都有一个 layer,view 持有并管理这个 layer,且这个 view 是 layer 的代理。
    区别
    UIView 负责响应事件,参与响应链,为 layer 提供内容。
    CALayer 负责绘制内容,动画。

    22. 什么是离屏渲染
    GPU 在当前屏幕缓冲区之外另开一片内存空间进行渲染操作。
    GPU 在绘制时没有回头路,某一个 layer 在被绘制之后就已经与之前的若干层形成一个整体了,无法再次修改。
    对于一些有特殊效果的 layer,GPU 无法通过单次遍历就能完成渲染,只能另外申请一片内存区域,借助这个临时区域来完成更加复杂、多次的修改与裁减操作。
    大小是屏幕像素点的2.5倍
    需要创建新的渲染缓冲区,会存在不小的内存开销
    且需要切换渲染上下文 Context Switch,会浪费很多时间

    23 http 和https

    24.什么是中间人攻击
    中间人攻击的定义:中间人攻击是指攻击者与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。
    简单来说,攻击者在请求和响应传输途中,拦截并篡改内容

    • 24 autoreleasepool及runloop的关系
      autoreleasepool简单说是双向链表,每张链表头尾相接,有 parent、child指针
      每创建一个池子,会在首部创建一个 哨兵 对象,作为标记
      最外层池子的顶端会有一个next指针。当链表容量满了,就会在链表的顶端,并指向下一张表

    1.@autoreleasepool展开来其实就是objc_autoreleasePoolPush和objc_autoreleasePoolPop,但是这两个函数也是封装的一个底层对象AutoreleasePoolPage,实际对应的是AutoreleasePoolPage::push和AutoreleasePoolPage::pop

    2.autoreleasepool本身并没有内部结构,而是一种通过AutoreleasePoolPage为节点的双向链表结构

    3.根据AutoreleasePoolPage双向链表的结构,可以看到当调用objc_autoreleasePoolPush的时候实际上除了初始化poolpage对象属性之外,还会插入一个POOL_SENTINEL哨兵,用来区分不同autoreleasepool之间包裹的对象。

    4.当对象调用 autorelease 方法时,会将实际对象插入 AutoreleasePoolPage 的栈中,通过next指针移动。

    5.autoreleasePoolPage的结构字段上面有介绍,其中每个双向链表的node节点也就是poolpage对象内存大小为4096,除了基础属性之外,外插一个POOL_SENTINEL,每出现一个@autorelease就会有一个哨兵,剩下的通过begin和end来标识是否存储满,满了就会重新创建一个poolpage来链接链表,按照这个套路,出现一个PoolPush就创建一个哨兵,出现一个对象的autorelease,就增加一个实际的对象,满了就创建新的链表节点这样衍生下去

    6.AutoreleasePoolPage::pop那么当调用pop的时候,会传入需要drain的哨兵节点,遍历该内存地址上方所有对象,直到遇到对应的哨兵,然后释放栈中遍历到的对象,每删除一页就修正双向链表的指针,最后两张图很容易理解

    7.ARC下,直接调用上面的方法,整个线程都被自动释放池双向链表管理,Push创建的时候插入哨兵对象,当我们在内部写代码的时候,会自动添加Autorelease,对象会加入到在哨兵节点之间,加入到next指针上,一个个往后移,满了4096就换下一个poolPage对象节点来存储,出了释放池,会调用pop,传入自动释放池的哨兵给pop,然后遍历哨兵内存地址之后的所有对象执行release,最后吧next指针移到目标哨兵

    App启动后,苹果在主线程 RunLoop 里注册了两个Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()

    第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。

    第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop()_objc_autoreleasePoolPush()释放旧的池并创建新池;Exit(即将退出Loop)时调用 objc_autoreleasePoolPop()来释放自动释放池。这个 Observerorder2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

    在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

    • 25 访问 __weak 修饰的变量,是否已经被注册在了 @autoreleasePool 中?为什么?
      答案是肯定的,__weak修饰的变量属于弱引用,如果没有被注册到 @autoreleasePool 中,创建之后也就会随之销毁,为了延长它的生命周期,必须注册到 @autoreleasePool 中,以延缓释放。

    25 categray相关
    https://www.jianshu.com/p/78e18cdbe23f

    • 26 dispatch_once实现原理
    + (instancetype)sharedInstance
    {
        /*定义相应类实例的静态变量;
        意义:函数内定义静态变量,无论该函数被调用多少次,
             在内存中只初始化一次,并且能保存最后一次赋的值
        */
        static ClassName *instance = nil;
        /*定义一个dispatch_once_t(其实也就是整型)静态变量,
        意义:作为标识下面dispatch_once的block是否已执行过。
             static修饰会默认将其初始化为0,当值为0时才会执行block。
             当block执行完成,底层会将onceToken设置为1,这也就是为什
             么要传onceToken的地址(static修饰的变量可以通过地址修改
             onceToken的值),同时底层会加锁来保证这个方法是线程安全的
        */
        static dispatch_once_t onceToken;
        /*只要当onceToken == 0时才会执行block,否则直接返回静态变量instance*/
        dispatch_once(&onceToken, ^{
            instance = [[ClassName alloc] init];
            //...
        });
        return instance;
    }
    
    • 27 散列表 哈希函数
      散列表用的是数组支持按照下标随机访问数据的特性,所以散列表其实就是数组的一种扩展,由数组演化而来

    散列冲突
    开放寻址法(open addressing)和链表法(chaining)
    我们用装载因子(load factor)来表示空位的多少。

    装载因子的计算公式是:

    散列表的装载因子 = 填入表中的元素个数 / 散列表的长度
    

    装载因子越大,说明空闲位置越少,冲突越多,散列表的性能会下降。

    散列表中,每个“桶(bucket)”或者“槽(slot)”会对应一条链表,所有散列值相同的元素我们都放到相同槽位对应的链表中
    当数据量比较小、装载因子小的时候,适合采用开放寻址法
    基于链表的散列冲突处理方法比较适合存储大对象、大数据量的散列表,而且,比起开放寻址法,它更加灵活,支持更多的优化策略,比如用红黑树代替链表。
    哈希算法:
    将任意长度的二进制值串映射为固定长度的二进制值串,这个映射的规则就是哈希算法,而通过原始数据映射之后得到的二进制值串就是哈希值
    哈希算法应用:
    分别是安全加密、唯一标识、数据校验、散列函数、负载均衡、数据分片、分布式存储。

    • 28 快速排序
    #define MAXSIZE 10000
    typedef struct
    {
        //r[0]用作哨兵或临时变量
        int r[MAXSIZE+1];
        int length;
    }SqList;
    
    void swap(SqList *L, int i, int j) {
        int a = L->r[i];
        int b = L->r[j];
        a ^= b;
        b ^= a;
        a ^= b;
    }
    
    int Partition(SqList *L, int low, int high) {
        int pivotkey;
        pivotkey = L->r[low];
        while (low < high) {
            while (low < high && L->r[high] >= pivotkey)
                high--;
            swa(L, low, high);
            while (low < high && L->r[low] <= pivotkey)
                low++;
            swa(L, low, high);
        }
        return low;
    }
    
    void QSort(SqList *L, int low, int high) {
        int pivot;
        if (low<high) {
            pivot = Partition(L, low, high);
            QSort(L, low, pivot-1);
            QSortd(L, pivot+1, high);
        }
    }
    
    void QuickSort3(SqList *L) {
        QSortd(L, 0, L->length);
    }
    
    
    • 值类型和引用类型的区别
      1、速度上的区bai别

    值类型存du取速度快,引用类型zhi存取速度慢。

    2、用途上的区别

    值类型表示实际数据,引用类dao型表示指向存储在内存堆中的数据的指针或引用。
    3、来源上的区别
    值类型继承自System.ValueType,引用类型继承自System.Object

    4、位置上的区别

    值类型的数据存储在内存的栈中,引用类型的数据存储在内存的堆中,而内存单元中只存放堆中对象的地址。
    5、类型上的区别

    值类型的变量直接存放实际的数据,而引用类型的变量存放的则是数据的地址,即对象的引用。

    6、保存位置上的区别

    值类型变量直接把变量的值保存在堆栈中,引用类型的变量把实际数据的地址保存在堆栈中,而实际数据则保存在堆中。

    相关文章

      网友评论

        本文标题:2020-07-05

        本文链接:https://www.haomeiwen.com/subject/xosvqktx.html