拓展:person. name = @"MD" 底层本质是什么?
-
看过
clang知道系统会为属性在编译时添加setter方法,使用=来赋值就相当于调用这个setter方法。它具体是怎么编译出来的呢?@interface LGPerson : NSObject // 属性 @property (nonatomic, copy) NSString *name; @end
本质的 setter方法 从何而来?
-
编译后,可以从 ro 里取出对应的
setter方法,断点调试看到,它来源于 objc底层 的这里(这里开一下上帝视角,跟汇编很繁琐的)
image.png
-
这几个方法区分
atomic/nonatomiccopy/notcopy,他们内部都调用下面这个reallySetProperty: -
reallySetProperty:,设置 newValue,释放 oldValue 等 常规操作 -
为什么不是 clang 看到的简简单单的
setName? 答案:通用入口,需要考虑atomic/nonatomiccopy/notcopy
image.png
系统如何走进 reallySetProperty通用入口
- 说白了就是 我们写的
person.name = @"MD"和objc_setProperty_nonatomic_copy之间有断层,objc源码 看不到,那么估计是 汇编、LLVM 或者 宏
使用 Visual Studio Code 查找 LLVM代码
-
LLVM源码,搜
objc_setProperty_nonatomic_copy,这个图没意思,下面那个有意思
image.png
-
根据
atomic和copy标示 来设置方法的名字 -
最后
return CGM.CreateRuntimeFunction(FTy, name); -
回溯搜索
image.png
-
回溯搜索
image.png
-
也没啥好看的
image.png
结论
-
编译时,
LLVM分析@property (nonatomic, copy) NSString *name;,根据nonatomic/copy赋予它一个合适的 setter方法,这次是objc_setProperty_nonatomic_copy -
运行时,
person.name = @"MD"时调起该 setter方法
KVC
成员属性和成员变量的KVC区别
-
setValue:forKey:时,成员属性name走objc_setProperty_nonatomic_copy,成员变量sex不走 -
很正常,因为属性才在
ro有getter/setter@interface LGPerson : NSObject { @public // 成员变量 NSString *sex; } // 属性 @property (nonatomic, copy) NSString *name; @endLGPerson *person = [[LGPerson alloc] init]; [person setValue:@"male" forKey:@"sex"]; [person setValue:@"Mark" forKey:@"name"]; -
但
sex也通过 KVC 成功赋值了,这是咋回事?
image.png
猜测
-
这个图也只是猜测
image.png
-
setValue:forKey:和前面的person.name = @"MD"一样,会在LLVM鬼鬼祟祟地做处理,很有可能调用了这个↓// 在汇编看到 msgSend,不一定直接是这个方法,但有可能 msgSend 的那个方法包含这个方法 object_setIvar(self , ivar, value);/** * Sets the value of an instance variable in an object. * * @param obj The object containing the instance variable whose value you want to set. * @param ivar The Ivar describing the instance variable whose value you want to set. * @param value The new value for the instance variable. * * @note Instance variables with known memory management (such as ARC strong and weak) * use that memory management. Instance variables with unknown memory management * are assigned as if they were unsafe_unretained. * @note \c object_setIvar is faster than \c object_setInstanceVariable if the Ivar * for the instance variable is already known. */ OBJC_EXPORT void object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); -
注意:编译时系统会给
成员属性创建getter/setter,所以接下来使用成员变量进行探索
根据文档验证 setValue:forKey: 流程
image.png
-
找对象方法:
setSex:或_setSex:(必须有参数),并执行。↓确实可以,.h文件不声明也可以- (void)setSex:(NSString *)sex { NSLog(@"AAA%s",__func__); } - (void)_setSex:(NSString *)sex { NSLog(@"BBB%s",__func__); }2020-03-24 09:05:17.352322+0800 objc-debug[8662:705820] AAA-[LGPerson setSex:] -
如果找不到,且如果
accessInstanceVariablesDirectly返回YES,则系统自行赋值给_sex,_isSex,sex或isSex。↓确实可以,把Person类的sex变量改成_isSex,明明 KVC 设 的是sex,结果_isSex被赋值了[person setValue:@"male" forKey:@"sex"]; // 赋值给 _isSex NSLog(@"%@", person->_isSex);2020-03-23 23:18:18.125211+0800 objc-debug[8152:636122] male- 这应该是为了容错,防止人为加
is或者运行时系统加_
- 这应该是为了容错,防止人为加
-
如果上面2个情况都不满足,则调用
setValue:forUndefinedKey:,默认会报异常,但NSObject的子类可能会重写该方法[person setValue:@"male" forKey:@"xxsex"];- 默认会报异常(不重写)
2020-03-24 08:49:54.644587+0800 objc-debug[8625:696532] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<LGPerson 0x102d5a010> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key xxsex.'- 在
Person.m重写
- (void)setValue:(id)value forUndefinedKey:(NSString *)key { NSLog(@"value is: %@, key is: %@", value, key); }2020-03-24 08:48:24.367977+0800 objc-debug[8597:694937] value is: male, key is: xxsex
根据文档验证 valueForKey: 流程
image.png
-
找对象方法:
getSex,sex,isSex或者_sex,如果找到就赋值,然后跳转到第5步,否则进入下一步。确实可以↓,刻意用了isSex方法,.h文件不声明也可以- (void)isSex { NSLog(@"LLL%s",__func__); }2020-03-24 09:27:06.501039+0800 objc-debug[8831:720436] LLL-[LGPerson isSex] -
关于NSArray的,这次先不看
-
关于NSSet的,这次先不看
-
如果
accessInstanceVariablesDirectly返回YES,则系统自行找成员变量_sex,_isSex,sex或isSex;如果能找到,则进行取值,然后进入下一步,否则跳转到第6步。↓确实可以,把Person类的sex变量改成isSex,明明 KVC 取 的是sex,结果能返回isSex的值@interface LGPerson : NSObject { @public // 成员变量 NSString *isSex; }[person setValue:@"male" forKey:@"sex"]; // 赋值给 isSex NSLog(@"%@", [person valueForKey:@"sex"]); // 取出 isSex2020-03-24 09:20:45.443098+0800 objc-debug[8788:716312] male -
如果
第1步或第4步顺利执行,会直接跳到这里。分3种情况:- 对象指针,则直接返回
- 能被
NSNumber支持的值类型,则存入并返回一个NSNumber对象 - 不能被
NSNumber支持的值类型,则转成NSValue对象并返回(PS:NSNumber是NSValue的子类)
[person setValue:@18 forKey:@"age"]; // age是成员变量int NSLog(@"%d", [[person valueForKey:@"age"] intValue]); // NSNumber2020-03-24 09:56:39.050970+0800 objc-debug[8921:735692] 18 -
如果上面的情况都不满足,则调用
valueForUndefinedKey:,默认会报异常,但NSObject的子类可能会重写该方法。上面setValue示范过了。
KVC 对一些特殊情况的处理
自动转换类型
// [person setValue:@18 forKey:@"age"]; // 正常情况
[person setValue:@"20" forKey:@"age"]; // 把 int 设值成 string
[[person valueForKey:@"age"] class]; // __NSCFNumber
设置空值
- (void)setNilValueForKey:(NSString *)key{
NSLog(@"设 %@ 为空值",key);
}
-
setNilValueForKey:(推测)原意是为了对值类型容错,所以只对NSNumber和NSValue生效,如果对一个String类型的name设为nil,不会走这个方法
image.png
设值找不到 Key
- (void)setValue:(id)value forUndefinedKey:(NSString *)key{
NSLog(@"设值,但没有这个key: %@ ",key);
}
取值找不到 Key
- (id)valueForUndefinedKey:(NSString *)key{
NSLog(@"取值,但没有这个key: %@ ; 返回一个自定义的值",key);
return @"自定义值";
}










网友评论