这是 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
的话不允许外部进行值的修改 -
strong
,weak
,assign
,copy
,unsafe_unretained
这里只大概说下weak
, 和assign
, 因为有的人会问这两个有什么区别
一般情况下weak
用来修饰对象类型,而assign
来修饰一般数据类型,id
类型看情况用,是因为weak
修饰的对象在释放后被自动置为 nil,而assign
不会有这部操作,所以语法上assign
也是可以修饰对象类型,但这样很容易野指针,所以一般用weak
修饰,但是weak
从语法上就不能修饰一般数据类型 -
setter
,getter
用来指定特定的 set 方法 和 get 方法 -
nonnull
,null_resettable
,nullable
nonnull
和weak
不可以同时存在
就算有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 关键字使用场景:
-
一般最多的是我们给属性类型是
NSString
,NSArray
,NSDictionary
, 这些用 copy 修饰 。
因为这几个类型有分别对应的 mutable 属性,而在我们写代码的时候经常会有要把 mutable 属性的值赋给 不可变属性,譬如:self.str1 = @""; NSMutableString *mutStr1 = [NSMutableString stringWithString:@"str1"]; _str1 = mutStr1; [mutStr1 appendString:@" - mutStr1"];
我们的初衷是给 _str1
赋值为 @"str1"
, 这种场景下,
如果 str1
是 strong
关键字修饰的,那么打印会发现 _str1
的 值为 @"str1 - mutStr1"
, 并且 _str1
的地址就是 mutStr1
的地址,
如果 str1
是 copy
关键字修饰的,那么打印会发现 _str1
的 值为 @"str1"
, 并且 _str1
的地址是一个新的地址,
显然第二种结果才是我们想要的, 所以NSString
,NSArray
,NSDictionary
这些属性用copy
来修饰。
扩展一下:
反过来NSMutableString
,NSMutableArray
,NSMutableDictionary
却不能用 copy
修饰,因为 copy
深拷贝后的值是一个 非mutable 的,这样你再增删改就会直接崩溃。
- 自定义类的对象用
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 表中删除。
网友评论
copyObject.str1 = _str1;
copyObject.str2 = _str2;
return copyObject;
CopyClass *copyObject = [[CopyClass alloc] init];
copyObject.str1 = copyObject.str1;
copyObject.str2 = copyObject.str2;
return copyObject;