KVC(Key-Value Coding)的属性查找逻辑是一个非常严密的“搜索算法”。它不仅查找 Getter/Setter 方法,还会根据特定规则尝试访问成员变量(Ivar)。
查找顺序主要分为 “设值 (Set)” 和 “取值 (Get)” 两个流程。
1. 设值流程:setValue:forKey:
当执行 setValue:value forKey:@"name" 时,查找顺序如下:
-
查找 Setter 方法:
按照顺序查找:
setName:_setName:。如果找到,直接传递参数执行。 -
查找接入点:
如果没有 Setter,KVC 会检查类方法
+accessInstanceVariablesDirectly。- 如果返回
NO(禁止直接访问变量),则跳转到第 4 步。 - 如果返回
YES(默认值),则进入第 3 步。
- 如果返回
-
按顺序查找成员变量(Ivar):
依次查找:
_name_isNamenameisName。 -
最终处理:
如果以上都没找到,调用
-setValue:forUndefinedKey:(默认抛出异常NSUndefinedKeyException)。
2. 取值流程:valueForKey:
当执行 [obj valueForKey:@"name"] 时,查找过程更为复杂,因为它涉及到了对集合类型的特殊支持:
-
查找 Getter 方法:
依次查找:
getNamenameisName_name。如果找到,执行并返回结果。 -
查找集合搜索方法(特殊优化):
如果没找到普通 Getter,KVC 会尝试寻找符合集合模式的方法(如
countOfName、objectInNameAtIndex:等)。如果找到,它会返回一个能够响应集合操作的代理对象。 -
查找接入点:
检查
+accessInstanceVariablesDirectly是否返回YES。 -
按顺序查找成员变量(Ivar):
依次查找:
_name_isNamenameisName。 -
最终处理:
如果还是没找到,调用
-valueForUndefinedKey:。
3. 核心机制总结
| 阶段 | 搜索优先级 (Key: "name") |
|---|---|
| 第一阶段 (Method) | setName: (Set) / getName (Get) |
| 第二阶段 (Access Check) | +accessInstanceVariablesDirectly |
| 第三阶段 (Ivar) | 下划线开头优先 (_name > _isName > name) |
| 第四阶段 (Fallback) | UndefinedKey 异常处理 |
4. 为什么 KVC 能够访问私有变量?
KVC 的强大(也伴随风险)之处在于它跳过了编译器的访问权限检查。即使你的 _name 定义在 .m 文件的类扩展中且没有暴露接口,KVC 依然能通过 Runtime 指针偏移 找到该变量并强行读写。
面试小贴士:
如果面试官问“如何禁用 KVC 访问私有变量?”,答案是重写
+accessInstanceVariablesDirectly并返回NO。此时 KVC 只能访问定义了 Setter/Getter 的属性。