美文网首页iOS 面试题面试
iOS : 一个区分很大的面试题

iOS : 一个区分很大的面试题

作者: 胡小夜大叔 | 来源:发表于2017-07-17 12:40 被阅读61次

    这是 sunny博客的一道面试题,还挺值得一说的,主要考基础扎实不扎实。

    考察一个面试者基础咋样,基本上问一个 @property 就够了:

    @property 后面可以有哪些修饰符?
    什么情况使用 weak 关键字,相比 assign 有什么不同?
    怎么用 copy 关键字?
    这个写法会出什么问题: @property (copy) NSMutableArray *array;
    如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?
    这一套问题区分度比较大,如果上面的问题都能回答正确,可以延伸问更深入点的:

    @property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的
    @protocol 和 category 中如何使用 @property
    runtime 如何实现 weak 属性
    每个人擅长的领域不一样,我们一般会从简历上找自己写擅长的技术聊,假如自己并不是很熟,最好别写出来或扯出来,万一面试官刚好非常精通这里就露馅了。

    1.@property 后面可以有哪些修饰符?
    • atomic, nonatomic
      原子,非原子,用来保障属性的线程安全的,一般用nonatomic
      因为 atomic 一方面也不能保证一定是线程安全的,另一方面消耗性能太大。
      如果不修饰,则默认是atomic
    • readonly, readwrite
      只读,或者可读可写,顾明思意,readonly 的话不允许外部进行值的修改
    • strongweak, assign, copyunsafe_unretained
      这里只大概说下 weak, 和 assign, 因为有的人会问这两个有什么区别
      一般情况下 weak 用来修饰对象类型,而 assign 来修饰一般数据类型, id 类型看情况用,是因为 weak 修饰的对象在释放后被自动置为 nil,而 assign 不会有这部操作,所以语法上 assign 也是可以修饰对象类型,但这样很容易野指针,所以一般用 weak 修饰,但是 weak 从语法上就不能修饰一般数据类型
    • setter, getter
      用来指定特定的 set 方法 和 get 方法
    • nonnull, null_resettable, nullable
      nonnullweak 不可以同时存在
      就算有 nonnull 修饰,属性依然可以设置为 nil, 只不过有个警告而已....

    2.什么情况使用 weak 关键字,相比 assign 有什么不同?

    一般weak使用的地方有:

    • 防止循环引用
      最典型的就是 delegate 一般要设置为 assign/weak ,和 block 内引用的的变量要在 block 之前 weak 申明一下
    • 被 xib 引用的
      xib 连线的的就是强引用,所以用 weak 就可以了

    weak 和 assign 的小区别:
    哎呀上面刚说了...复制一下吧....

    一般情况下 weak 用来修饰对象类型,而 assign 来修饰一般数据类型, id 类型看情况用。是因为 weak 修饰的对象在释放后被自动置为 nil,而 assign 不会有这部操作,所以语法上 assign 也是可以修饰对象类型,但这样很容易野指针,所以一般用 weak 修饰,但是 weak 从语法上就不能修饰一般数据类型


    3.怎么用 copy 关键字?

    要知道怎么要用copy关键字我们首先得知道深浅拷贝的概念:

    • 浅拷贝:不产生新的内存空间,只是多一个指针,指向拷贝内容地址
    • 深拷贝:产生新的内存空间,并把拷贝内容复制到新的内存空间中

    然后我们在看下 copy 关键字使用场景:

    • 一般最多的是我们给属性类型是 NSStringNSArrayNSDictionary, 这些用 copy 修饰 。
      因为这几个类型有分别对应的 mutable 属性,而在我们写代码的时候经常会有要把 mutable 属性的值赋给 不可变属性,譬如:

      self.str1 = @"";
      NSMutableString *mutStr1 = [NSMutableString stringWithString:@"str1"];
      _str1 = mutStr1;
      
      [mutStr1 appendString:@" - mutStr1"];
      

    我们的初衷是给 _str1 赋值为 @"str1" , 这种场景下,
    如果 str1strong 关键字修饰的,那么打印会发现 _str1 的 值为 @"str1 - mutStr1" , 并且 _str1 的地址就是 mutStr1 的地址,
    如果 str1copy 关键字修饰的,那么打印会发现 _str1 的 值为 @"str1" , 并且 _str1 的地址是一个新的地址,
    显然第二种结果才是我们想要的, 所以NSStringNSArrayNSDictionary这些属性用copy来修饰。

    扩展一下:
    反过来NSMutableStringNSMutableArrayNSMutableDictionary 却不能用 copy 修饰,因为 copy 深拷贝后的值是一个 非mutable 的,这样你再增删改就会直接崩溃。

    image.png image2.png
    • 自定义类的对象用 copy 修饰
      这就需要我们给自定义类实现 NSCopying 协议

    4.这个写法会出什么问题: @property (copy) NSMutableArray *array;

    哎呀上面也刚说了...再复制一下吧....

    反过来NSMutableString,NSMutableArray,NSMutableDictionary 却不能用 copy 修饰,因为 copy 深拷贝后的值是一个 非mutable 的,这样你再增删改就会直接崩溃。

    其实只要理解了深浅拷贝,这些很容易理解,就不详细举例子了


    5.如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?

    要想自己的类能用 copy 修饰符,就要给实现 NSCopying 协议, 要是有 mutable 需求的就实现 NSMutableCopying

    如何重写带copy关键字的setter :

    假如我们有一个类实现了 NSCopying 协议:

    @interface CopyClass : NSObject <NSCopying>
    @property (nonatomic, strong) NSString *str1;
    @property (nonatomic, strong) NSString *str2;
    @end
    
    @implementation CopyClass 
    - (id)copyWithZone:(nullable NSZone *)zone
    {
        CopyClass *copyObject = [[CopyClass alloc] init];
        copyObject.str1 = copyObject.str1;
        copyObject.str2 = copyObject.str2;
        return copyObject;
    }
    @end
    

    那么我们在外部调用假如是这样的:

    @property (nonatomic, copy) CopyClass *myCopyB;
    

    调用的地方:

    CopyClass *copyA = [[CopyClass alloc] init];
    copyA.str1 = @"str1";
    copyA.str2 = @"str2";
    
    self.myCopyB = copyA;
    

    一般如果属性带 copy 关键字 ,那么 setter 的默认实现就是类似这样:

    -(void)setMyCopyB:(CopyClass *)myCopyB
    {
        _myCopyB = [myCopyB copy];
    }
    

    那这道题说如何重写带 copy 关键字的 setter,该咋写咋写呗,这还能玩出花来?不太懂这第二问的意思.....


    6.@property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的

    __@property 的本质就是 ivar + getter + setter __
    我们往一个类里添加一个属性,然后用runtime方法去观察,就可以知道,这个类的
    objc_ivar_list 里添加了对应的变量,methodLists 添加了对应的 sette 和 getter

    那么ivar、getter、setter 是如何生成并添加到这个类中的
    编译期,编译器会自动生成这个属性的 setter 和 getter 代码,并且向类中添加对应的实例变量,生成的结果和属性的声明是否有setter、getter关键字,还有@synthesize指定的变量名有关系。

    也就是说我们每次在增加一个属性,系统都会在 ivar_list中添加一个成员变量的描述,在 method_list中增加 setter 与 getter 方法的描述,在属性列表中增加一个属性的描述,然后计算该属性在对象中的偏移量,然后给出 setter 与 getter 方法对应的实现,在 setter 方法中从偏移量的位置开始赋值,在 getter 方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转.


    7.@protocol 和 category 中如何使用 @property

    我们知道在类里定义一个属性时,会在该类的ivar_list添加ivar描述,会在method_list添加该属性的seter和geter方法描述,在property_list中添加一个属性描述。

    但在@protocol 和 category 中如果定义一个 @property 时,只会在property_list中添加一个属性描述,method_list 和 ivar_list中都不会有相关的描述添加。

    所以要想@protocol 和 category 中的 @property 有意义,就必须在类中或category中,实现这个@property的setter和getter。

    但是 ivar_list 是没有该属性对应的 ivar, 所以 setter 中你是没法赋值的, 这时候就要用到 objc_setAssociatedObject 了,在 setter 中用 objc_setAssociatedObject 赋值,并在 getter 中用 objc_getAssociatedObject 取值,这样就变相完成了@protocol 和 category 中属性的设置


    8.runtime 如何实现 weak 属性

    首先我们要知道 weak 关键字的一些点:

    • 不添加引用计数,既不持有,只是单纯的指向
    • 释放的时候会置为nil

    存放weak对象映射的表是由一个自旋锁管理的哈希表,当有 weak 引用时,会把 weak 对象指向的对象那个内存地址作为 weak 表的 key, 然后 value 就是 weak 对象的地址,当指向的那个对象被释放的时候, 会拿着被释放对象的地址来在 weak 里查,查到的对应的 weak 对象就会被置为nil, 然后从 weak 表中删除。


    相关文章

      网友评论

      • 丶拾光:应该是 CopyClass *copyObject = [[CopyClass alloc] init];
        copyObject.str1 = _str1;
        copyObject.str2 = _str2;
        return copyObject;
      • 丶拾光:nscopying那里代码写错了
        CopyClass *copyObject = [[CopyClass alloc] init];
        copyObject.str1 = copyObject.str1;
        copyObject.str2 = copyObject.str2;
        return copyObject;

      本文标题:iOS : 一个区分很大的面试题

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