iOS面试题总结(一)

作者: 文小猿666 | 来源:发表于2020-05-10 23:09 被阅读0次

    1.为什么不能给类别category 添加成员变量?extension呢?

    2.isKindOfClass: 和 -isMemberOfClas区别?

    3.weak的实现原理

    4.理解 [self class] 与 [super class] ?

    5.ios中的内存管理机制

    6.Block如何访问外部变量? 下划线__block的作用? 如何防止循环引用?

    7.Block循环引用问题
    (1).为什么Msonry不会循环引用?
    (2).weakSelf、strongSelf结合使用

    8.深拷贝与浅拷贝

    9.KVO实现原理

    10.Runtime的原理及实际使用场景

    讲解

    1.为什么不能给类别category 添加成员变量?extension呢?
    分类并不会改变原有类的内存分布的情况,它是在运行期间决定的,此时内存的分布已经确定,若此时再添加实例会改变内存的分布情况,这对编译性语言是灾难,是不允许的。
    category 是基于运行时的:

    typedef struct category_t {
    const char *name; //类的名字
    classref_t cls; //类
    struct method_list_t *instanceMethods; //category中所有给类添加的实例方法的列表
    struct method_list_t *classMethods; //category中所有添加的类方法的列表
    struct protocol_list_t *protocols; //category实现的所有协议的列表
    struct property_list_t *instanceProperties; //category中添加的所有属性
    } category_t;
    

    结构体中并没有成员变量这个list,所以无法在category添加成员变量,添加属性是可以的,但是不会生成添加属性的getter和setter方法,所以,尽管添加了属性,也无法使用点语法调用getter和setter方法,但你可以使用运行时实现关联对象并可以引用。

    反观扩展(extension),作用是为一个已知的类添加一些私有的信息,必须有这个类的源码,才能扩展,它是在编译器生效的,所以能直接为类添加属性或者实例变量。

    category跟extension最大的区别在于生效时间不一样,category在运行时生效,而extension在编译时生效

    2.isKindOfClass: 和 -isMemberOfClas区别?
    isKindOfClass来确定一个对象是否是一个类的成员,或者是派生自该类的成员。
    isMemberOfClass只能确定一个对象是否是当前类的成员。

    3.weak的实现原理

    weak是Runtime维护了一个hash(哈希)表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。

    对象准备释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

            A.x = B;
     __weak B.y = A;
    

    当A释放时,会根据A的地址获取所有弱引用它的指针的地址(如B.y),把它置为nil。
    4.理解 [self class] 与 [super class] ?

    我们知道实际上在iOS中,对方法的调用是通过发送消息来完成的。也就是说使用 [self class] 时,会使用obj_msgSend(id theReceiver, SEL selector, ...)函数向Receiver来发送消息。而使用 [super class] 时,会使用obj_msgsendSuper(...)函数向Receiver来发送消息。
    ————————————————

    @implementation Son : Father 
    - (id)init 
    { 
    self = [super init]; 
    if (self) { 
    NSLog(@”%@”, NSStringFromClass([self class])); 
    NSLog(@”%@”, NSStringFromClass([super class])); 
    } 
    return self; 
    } 
    @end
    

    上边代码会输出什么,为什么
    简单来说,self和super都是指向当前实例的,不同的是,[self class]会在当前类的方法列表中去找class这个方法,[super class]会直接开始在当前类的父类中去找calss这个方法,两者在找不到的时候,都会继续向祖先类查询class方法,最终到NSObject类。那么问题来了,由于我们在Father和Son中都没有去重写class这个方法,最终自然都会去执行NSObject中的class方法,结果也自然应该是一样的。
    至于为什么是Son,我们可以看看NSObject中class的实现:

    -(Class)class { 
    return object_getClass(self); 
    }
    

    5.ios中的内存管理机制
    为了管理所有对象的引用计数和weak指针,苹果创建了一个全局的SideTables,虽然名字后面有个"s"不过他其实是一个全局的Hash表,里面的内容装的都是SideTable结构体而已。它使用对象的内存地址当它的key。管理引用计数和weak指针就靠它了。

    图片.png

    6.Block如何访问外部变量? 下划线__block的作用? 如何防止循环引用?

    • block在访问外部变量(局部变量)时,会在block内部创建(浅拷贝)一个新的变量来保存这个外部变量,修改的是内部新变量的值,外部的变量不受影响;

    • 当外部变量加了__block修饰,block保存(深拷贝)了其指针引用,对指针变量的修改直接影响外部变量的值;
      另外,静态变量、全局变量和全局静态变量,传入的就是地址值,可以直接被block修改

    • 为了避免循环引用,最好的是对block内部引用的self对象进行弱引用。一般使用一个弱指针来指向该对象,然后在block内使用该弱引用指针来进行操作,这样就避免了block对该对象的强引用。

    7.Block循环引用问题
    (1).为什么Msonry不会循环引用?
    查看masonry源码可以看到究竟:msonry中设置布局的方法中的block对象并没有被View所引用,而是直接在方法内部同步执行,执行完以后block将释放,其中捕捉的外部变量的引用计数也将还原到之前。
    (2).weakSelf、strongSelf结合使用
    使用weakSelf结合strongSelf的情况下,能够避免循环引用,也不会造成提前释放导致block内部代码无效(野指针问题)

    _person1 = [[Person alloc] init];
    _person2 = [[Person alloc] init];
    _person2.name = @"张三";
    __weak __typeof(self) weakSelf = self;
    _person1.block = ^{
        __typeof(&*weakSelf) strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",strongSelf.person2.name);
        });
    };
    

    8.深拷贝与浅拷贝
    浅拷贝就是拷贝之后,并没有真正的复制,而是复制对象和原对象都指向同一个地址
    深拷贝是真正的复制了一份,复制的对象只想新的地址

    • copy:对于可变对象为深拷贝,对于不可变对象为浅拷贝
    • mutablecopy:始终为深拷贝
      注:
      oc中只有遵循<NSCopying>才支持copy,只有遵循<NSMutableCopying>才支持mutablecopy,如果没有遵循,拷贝时会直接crash。

    9.KVO实现原理
    当某个类的对象属性第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter方法。派生类在被重写的setter方法内实现真正的通知机制。
    当观察对象A时,KVO机制动态创建一个新的名为NSKVONotifying_A的新类,该类集成字对象A的本类,且KVO为NSKVONotifying_A重写观察属性的setter方法,setter方法会负责在调用元setter方法之前和之后,通知所有观察对象属性值的更改情况。
    被观察属性发生改变之前,willChangeValueForkey:被调用,通知系统该keyPath的属性值即将变更;当改变发生后,didChangeValueForkey:被调用,通知系统该keyPath的属性值已经变更;之后,observeValueForKey:ofObject:context:也会被调用。且重写观察属性的setter方法这种继承方式的注入是在运行时而不是编译时实现的。

    10.Runtime的原理及实际使用场景
    主动使用
    1.字典转模型
    2.给分类属性添加get,set方法
    3.方法交换swizzling
    4.设置UITextField占位文字的颜色
    ···

    隐式调用
    1.KVO与KVC的实现。
    2.内存管理,weak表的维护。
    ···

    相关文章

      网友评论

        本文标题:iOS面试题总结(一)

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