iOS中KVC与KVO工作原理及高阶面试题解析

iOS中KVC与KVO工作原理及面试题解析


一、KVC(Key-Value Coding)工作原理


核心机制:通过字符串键动态访问对象属性,基于NSKeyValueCoding协议实现。


1. 取值流程(valueForKey:)


按顺序查找getKey/key/isKey方法 → 检查accessInstanceVariablesDirectly是否允许 → 按_key/_isKey/key/iskey顺序访问成员变量 → 调用valueForUndefinedKey:(默认抛出异常)。


示例:若属性为age,优先调用getAge或age方法,若无则查找_age成员变量。


2. 赋值流程(setValue:forKey:)


按顺序查找setKey:/_setKey:方法 → 检查accessInstanceVariablesDirectly → 按_key/_isKey/key/iskey顺序赋值成员变量 → 调用setValue:forUndefinedKey:(默认抛出异常)。


示例:调用setValue:@10 forKey:@"age"时,优先调用setAge:方法,若无则直接修改_age变量。


3. 特殊场景处理


集合属性:若属性为NSArray,KVC会查找countOfKey、objectInKeyAtIndex等方法,返回代理集合NSKeyValueArray。


私有变量:KVC可直接访问以_开头的私有成员变量,但需accessInstanceVariablesDirectly返回YES(默认允许)。


4. 安全使用建议


避免使用基本数据类型(如int),改用NSNumber,防止后台返回null导致崩溃。


重写valueForUndefinedKey:和setValue:forUndefinedKey:处理未定义属性,避免程序崩溃。


二、KVO(Key-Value Observing)工作原理


核心机制:基于KVC和动态派发技术,通过isa-swizzling生成子类实现。


1. 底层实现


动态子类:当对象被观察时,系统生成NSKVONotifying_OriginalClass子类,重写被观察属性的setter方法。


isa指针修改:将原对象的isa指针指向动态子类,使setter调用重定向至子类实现。


通知触发:子类setter中调用willChangeValueForKey:和didChangeValueForKey:,最终触发观察者的observeValueForKeyPath:ofObject:change:context:回调。


2. 触发条件


必须通过KVC或setter方法修改属性值,直接修改成员变量不会触发KVO。


集合属性:需使用mutableArrayValueForKey:等方法获取代理对象,通过代理对象操作集合才能触发通知。


3. 手动触发KVO


直接调用willChangeValueForKey:和didChangeValueForKey:,适用于属性值依赖其他计算的场景。

- (void)setCustomValue:(id)value {

  [self willChangeValueForKey:@"customValue"];

  _customValue = value;

  [self didChangeValueForKey:@"customValue"];

}

4. Swift中的限制


属性需声明为@objc dynamic,且类必须继承自NSObject。


观察者需显式注册,且keyPath为字符串,可能引发编译时错误。


三、高阶面试题及答案


1. KVO的本质是什么?如何实现动态子类?


答案:KVO通过isa-swizzling生成NSKVONotifying子类,重写setter方法并插入通知逻辑。动态子类的isa指针指向该子类,使setter调用被拦截。


细节:子类重写class方法返回原类名,欺骗外部调用者;重写dealloc释放资源。


2. 直接修改成员变量会触发KVO吗?为什么?


答案:不会。因为KVO依赖setter方法或KVC操作,直接修改成员变量不会调用willChangeValueForKey:和didChangeValueForKey:。


3. 如何在Swift中正确使用KVO?


答案:


属性需@objc dynamic修饰,类继承自NSObject。


显式注册观察者:addObserver(self, forKeyPath: "property", options: .new, context: nil)。


实现observeValue(forKeyPath:of:change:context:)回调。


4. KVO如何处理集合属性的变化?


答案:使用mutableArrayValueForKey:获取代理对象,通过该对象操作集合(如addObject:),系统会自动触发通知。直接修改数组内容(如array[0] = newValue)不会触发KVO。


5. KVO的context参数有什么作用?


答案:用于区分不同的观察者或属性。在注册时传入唯一值(如静态变量地址),回调中通过context判断触发来源,避免继承或多监听时的混淆。


6. KVC赋值时,系统如何查找属性?


答案:按顺序查找setKey: → _setKey: → 检查accessInstanceVariablesDirectly → 查找_key/_isKey/key/iskey成员变量。若全部失败,调用setValue:forUndefinedKey:。


7. KVO的性能问题如何优化?


答案:


减少不必要的观察,及时调用removeObserver:。


使用Combine框架替代KVO,避免动态子类和isa-swizzling的开销。


合并多次属性变化,通过字典缓存最新值,批量处理通知。


8. KVC的安全使用需要注意什么?


答案:


避免使用基本数据类型,改用NSNumber防止null崩溃。


重写setValue:forUndefinedKey:和valueForUndefinedKey:处理未定义属性。


谨慎处理accessInstanceVariablesDirectly,避免意外访问私有变量。


四、总结


KVC和KVO是iOS开发中的核心机制,需深入理解其底层实现以应对高阶问题。面试中需关注动态子类、isa-swizzling、集合处理、Swift限制等细节,并结合实际场景分析优化策略。掌握这些知识不仅能提升代码质量,还能在面试中展现深度技术理解。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容