前言
本文翻译自苹果文档Accessor Search Implementation Details及方法的注释。翻译的不对的地方还请多多包涵指正,谢谢~
翻译背景
在做热修复的过程中,看到JSPatch的OC setter方法转义成JavaScript代码时,感到奇妙。代码如下:
@interface WMPatchTest
@property (nonatomic, strong) NSString *name;
@end
@implementation WMPatchTest
- (void)setName:(NSString *)name {
_name = name;
}
@end
defineClass('WMPatchTest', {
setPayCompletion: function(name) {
self.setValue_forKey(name, "_name");
},
});
代码中在使用setKey:value:函数时,用的是_name
,直接对protected属性_name
赋值。在用@property作属性声明,且getter和setter方法没有都手动同时实现情况下,系统会自动创建一个protected属性,属性名是在property前面加上下划线_ property。
Then,若想再深入具体了解KVC,请看下面翻译的苹果文档~
KVC访问器实现详细
在KVC直接访问实例变量前,会尝试使用属性的访问方法。本篇文章讲述了KVC是如何决定用哪种方法访问属性。
对于简单属性的-setValue:forKey:
当-setValue:forKey:
方法对一个属性的默认实现调用后,判断执行的顺序如下:
- 首先搜索调用类的实例方法,实例方法的名字模式是
set<Key>
(即set字符串和key值的组合)。如果方法找到了,系统还会校验方法参数。如果参数类型不是对象指针(id),但参数值为nil,则-setNilValueForKey:
被调用。-setNilValueForKey:
默认实现会抛出一个NSInvalidArgumentException
异常,但你可以重写这个方法的实现。如果参数类型是对象指针,方法会简单地校验通过。若参数类型是其他类型(比如,int,CGPoint等),在方法-valueForKey:
调用前会自动把这些类型转换成NSNumber或NSValue; - 如果方法没找到,而且调用该方法的类方法
+accessInstanceVariablesDirectly
返回的是YES,那么会查找该类实例的变量名字,依次匹配这些样式:_<key>, _is<Key>, <key>, is<Key>
。如果找到匹配的变量,且变量是对象型,那么对象引用计数会增1且变量也会被赋值,之后原来变量指向的旧值引用计数会减1。若实例变量是其他类型(比如,int,CGPoint等),会想步骤1中一样,先把NSNumber或NSValue转成非对象类型再赋值; - 否则(方法和实例变量都没找到),会调用
-setValue:forUndefinedKey:
方法。该方法默认实现会抛出一个NSUndefinedKeyException
异常,但你可以重写它;
兼容性注意:
- 对于
-takeValue:forKey:
方法的向后二进制兼容,在步骤1中若方法名模式是-_set<Key>:
也会被识别。不过在Mac10.3系统后,KVC中以下划线开头的模式方法已废弃;- 对于向后二进制兼容性,如果调用类参数不是对象型,则在步骤1中
-unableToSetNilForKey:
方法会代替-setNilValueForKey:
调用;- 步骤2中描述的行为与
-takeValue:forKey:
不同,后者在搜索类实例变量时只会去依次匹配<key>, _<key>
模式;- 步骤3中对于
-takeValue:forKey:
方法,如果调用类参数不是对象型,-handleTakeValue:forUnboundKey:
会代替-setValue:forUndefinedKey:
的调用;
栗子分析
再回头看背景介绍的例子,name是WMPatchTest类属性,编译器会自动生成setter方法-setName:
,getter方法-name
,及私有变量_name
。执行[WMPatchTest setValue:@"xx" forKey:@"_name"]时,首先进行步骤1,查找名字为set_name
方法。发现没有后,进行步骤2,检查发现+accessInstanceVariablesDirectly
为YES后(默认是YES),依次让类变量匹配这些样式:_name, _isName, name, isName
,_name
被匹配并执行赋值操作;
后语
开发中,KVC的设置方法使用很频繁,深入理解内部细节有助于我们开发提供更多思路,遇到异常情况时思路更清晰~