拓展:person.name = @"MD" 底层本质是什么?
-
看过
clang
知道系统会为属性
在编译时添加setter方法
,使用=
来赋值就相当于调用这个setter方法
。它具体是怎么编译出来的呢?@interface LGPerson : NSObject // 属性 @property (nonatomic, copy) NSString *name; @end
本质的 setter方法 从何而来?
-
编译后,可以从 ro 里取出对应的
setter方法
,断点调试看到,它来源于 objc底层 的这里(这里开一下上帝视角,跟汇编很繁琐的) -
这几个方法区分
atomic/nonatomic
copy/notcopy
,他们内部都调用下面这个reallySetProperty:
-
reallySetProperty:
,设置 newValue,释放 oldValue 等 常规操作 -
为什么不是 clang 看到的简简单单的
setName
? 答案:通用入口,需要考虑atomic/nonatomic
copy/notcopy
系统如何走进 reallySetProperty通用入口
- 说白了就是 我们写的
person.name = @"MD"
和objc_setProperty_nonatomic_copy
之间有断层,objc源码 看不到,那么估计是 汇编、LLVM 或者 宏
使用 Visual Studio Code 查找 LLVM代码
-
LLVM源码,搜
objc_setProperty_nonatomic_copy
,这个图没意思,下面那个有意思 -
根据
atomic
和copy
标示 来设置方法的名字 -
最后
return CGM.CreateRuntimeFunction(FTy, name);
-
回溯搜索
-
回溯搜索
-
也没啥好看的
结论
-
编译时,
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; @end
LGPerson *person = [[LGPerson alloc] init]; [person setValue:@"male" forKey:@"sex"]; [person setValue:@"Mark" forKey:@"name"];
-
但
sex
也通过 KVC 成功赋值了,这是咋回事?
猜测
-
这个图也只是猜测
-
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: 流程
-
找对象方法:
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: 流程
-
找对象方法:
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"]); // 取出 isSex
2020-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]); // NSNumber
2020-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
,不会走这个方法
设值找不到 Key
- (void)setValue:(id)value forUndefinedKey:(NSString *)key{
NSLog(@"设值,但没有这个key: %@ ",key);
}
取值找不到 Key
- (id)valueForUndefinedKey:(NSString *)key{
NSLog(@"取值,但没有这个key: %@ ; 返回一个自定义的值",key);
return @"自定义值";
}