首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

InterView-OC基础-3.KVO

  • 25-04-22 09:20
  • 3359
  • 12149
juejin.cn

0.复习流程

  • 先回答键值监听的概念
  • 说一下简单使用

2. 回答出通过isa推出的派生类的概念:

  • 借助runtime的 object_getClass()传入对象返回其类对象,传入类对象,返回其元类对象,也就是相当于isa的作用,分别打印其被监听前后
swift
代码解读
复制代码
self.p1 = [[Person alloc] init]; NSLog(@"监听前----%@",object_getClass(self.p1)); [self.p1 addObserver:self forKeyPath:@"age" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil]; NSLog(@"监听后----%@",object_getClass(self.p1)); self.p2 = [[Person alloc] init];

打印结果为

css
代码解读
复制代码
监听前----Person 监听后----NSKVONotifying_Person
  • 断点调试,在监听前 和 监听后分别在控制台p 对象.isa,派生类产生回答出runtime
swift
代码解读
复制代码
监听前 p self.person.isa (Class) $5 = Person 监听后 p self.person.isa (Class) $6 = NSKVONotifying_Person

3. 回答出重写setter方法

通过 - (IMP)methodForSelector:(SEL)aSelector方法来获取方法的实现,以此来查看,添加监听前后,setter方法实现是否进行了更改,代码如下

swift
代码解读
复制代码
self.p2 = [[Person alloc] init]; IMP imp1 = [self.p2 methodForSelector:@selector(setAge:)]; [self.p2 addObserver:self forKeyPath:@"age" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil]; IMP imp2 = [self.p2 methodForSelector:@selector(setAge:)];

打印结果如下 image.png 可以看出,被监听以后,setter方法实现变为了_NSSetIntValueAndNotify,这方法可以改为 _NSSet*AndNotify() 这个 * 的意思根据监听属性的不同,实现也不一样,具体有

代码解读
复制代码
_NSSetBoolValueAndNotify _NSSetCharValueAndNotify _NSSetDoubleValueAndNotify _NSSetFloatValueAndNotify _NSSetIntValueAndNotify _NSSetLongLongValueAndNotify _NSSetLongValueAndNotify _NSSetObjectValueAndNotify _NSSetPointValueAndNotify _NSSetRangeValueAndNotify _NSSetRectValueAndNotify _NSSetShortValueAndNotify _NSSetSizeValueAndNotify _NSSetUnsignedCharValueAndNotify _NSSetUnsignedIntValueAndNotify _NSSetUnsignedLongLongValueAndNotify _NSSetUnsignedLongValueAndNotify _NSSetUnsignedShortValueAndNotify
  • 这个c函数有三点作用,并且调用顺序如下
objectivec
代码解读
复制代码
[self willChangeValueForKey:@"属性名"]; setter [self didChangeValueForKey:@"属性名"];

我们通过重写这个类的以上三个方法,并打印,可以看出调用过程

objectivec
代码解读
复制代码
- (void)setAge:(int)age { _age = age; NSLog(@"setAge"); } - (void)willChangeValueForKey:(NSString *)key { [super willChangeValueForKey:key]; NSLog(@"willChangeValueForKey"); } - (void)didChangeValueForKey:(NSString *)key { NSLog(@"didChangeValueForKey -- begin"); [super didChangeValueForKey:key]; NSLog(@"didChangeValueForKey -- end"); }

我们在给person的age进行赋值以后,控制台打印结果为

arduino
代码解读
复制代码
willChangeValueForKey setAge didChangeValueForKey -- begin didChangeValueForKey -- end

4. 对class 方法,dealloc ,_isKVOA的重写

首先通过运行时来验证,在被监听以后的这个对象的类对象的方法列表,开看出以上方法被重写了,如果没有重写,以上方法不应该出现在Person的类结构中,而应该出现父类或者基类的方法里列表中

swift
代码解读
复制代码
self.p3 = [[Person alloc] init]; [self printMethodNamesOfClass:object_getClass(self.p3)]; [self.p3 addObserver:self forKeyPath:@"age" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil]; [self printMethodNamesOfClass:object_getClass(self.p3)];

打印结果为

objectivec
代码解读
复制代码
Person setAge:, age, NSKVONotifying_Person setAge:, class, dealloc, _isKVOA,

明显可以看出增加了三个方法,其中printMethodNamesOfClass是打印一个类的方法列表,实现源码为

scss
代码解读
复制代码
- (void)printMethodNamesOfClass:(Class)cls { unsigned int count; // 获得方法数组 Method *methodList = class_copyMethodList(cls, &count); // 存储方法名 NSMutableString *methodNames = [NSMutableString string]; // 遍历所有的方法 for (int i = 0; i < count; i++) { // 获得方法 Method method = methodList[i]; // 获得方法名 NSString *methodName = NSStringFromSelector(method_getName(method)); // 拼接方法名 [methodNames appendString:methodName]; [methodNames appendString:@", "]; } // 释放 free(methodList); // 打印方法名 NSLog(@"%@ %@", cls, methodNames); }

1. class

  • 证明方式:就是通过打印person的class方法调用返回值和object_getClass(person对象)来查看,在正常情况下,二者都应该返回person对象的类对象,也就是Person,但是我们在前面章节可以看出,object_getClass返回的值是NSKVONotifying_Person ,但是调用class返回的* 依旧是Person类,可以看出对象被监听以后,class方法被重写了 目的:为了屏蔽代码的实现逻辑,隐藏了NSKVONotifying_Person 的派生类,避免对KVO实际逻辑的探索

5.手动触发KVO

手动调用以下两行代码即可

objectivec
代码解读
复制代码
[self willChangeValueForKey:@"属性名"]; setter [self didChangeValueForKey:@"属性名"];

6.直接修改成员变量不会触发KVO

因为没有涉及到触发setter方法

7.常见面试题

1.当我们使用 KVO 监听数组或集合类型的属性时会发生什么?有哪些注意事项?

在使用 KVO(Key-Value Observing)监听数组或集合类型的属性时,会有一些特定的行为和注意事项。下面是一些关键点:

1. KVO 工作机制

KVO 是通过监听对象的属性变化来触发回调的。当属性的值发生变化时,KVO 会通知观察者。例如,如果一个对象的数组属性发生了变化(如添加或删除元素),KVO 会触发观察者的回调。

2. 数组或集合类型的特性

对于数组或集合(如 NSArray, NSMutableArray, NSSet, NSMutableSet),KVO 主要监听的是对象本身的属性变化。具体来说,数组的变化(如元素的增加、删除或重新排序)会通过 willChange 和 didChange 来进行通知。

  • 对于可变集合(如 NSMutableArray 或 NSMutableSet),在修改集合内容时,KVO 会触发 willChange 和 didChange 通知,表示集合将发生变化。
  • 对于不可变集合(如 NSArray 或 NSSet),一旦对象的内容改变,KVO 也会发出通知。

3. KVO 无法监听元素内部变化

如果你对数组或集合内的元素进行修改(如修改元素的属性),KVO 不会通知观察者。也就是说,KVO 只监听集合对象的引用变化(如集合本身的增加或删除元素),而不会监听元素的内部变化。

如果需要监听元素属性的变化,必须对每个元素进行单独的 KVO 监听。

4. 需要确保线程安全

在修改数组或集合的内容时(例如在多线程环境下),需要特别注意线程安全问题。直接修改集合内容可能会引发崩溃或其他未定义行为。因此,应确保在修改集合时对其进行适当的同步。

5. 需要手动观察

对于数组或集合的 KVO 监听,通常需要手动添加和移除观察者。在不再需要监听时,记得调用 removeObserver 移除观察者,以避免内存泄漏或崩溃。

6. 通知传递的粒度

KVO 仅在整个集合的引用发生变化时通知观察者。换句话说,某个元素被删除或替换时,KVO 会通知你集合的变化,但如果集合中的一个元素被修改(例如它的属性被改变),KVO 不会通知你。

7. 一些常见的变更

  • 对可变数组或集合的元素进行 addObject 或 removeObject 操作时,KVO 会通知观察者。
  • 对集合本身重新赋值或改变引用时,KVO 也会生效。

8. 例外情况:NSMutableArray 的 KVO 通知

当你使用 NSMutableArray 时,如果添加、删除或替换元素时,KVO 会触发通知。通常,如果你想监听数组的变化,你可以监听数组的 count 属性或者通过观察 mutableArrayValueForKey: 进行监听。

总结

  • KVO 能监听数组或集合对象的引用变化(如增加、删除或替换元素),但无法监听集合内元素的变化。
  • 在多线程环境下使用 KVO 时,要注意线程安全。
  • 如果数组或集合元素的内部状态发生变化,需为每个元素单独添加 KVO 观察。

2/2

4o mini

您说:
  1. 现代 KVO API 与旧版 API 有什么不同?为什么推荐使用现代 API?
ChatGPT 说:

ChatGPT

现代 KVO(Key-Value Observing)API 与旧版 KVO API 主要有以下几个关键的区别。随着 iOS 和 macOS 平台的发展,Apple 引入了新的 KVO API,旨在提高性能、减少错误并简化代码的管理。以下是两者的主要区别和现代 API 的推荐原因:

1. 旧版 KVO API

在旧版 KVO API 中,观察者通常通过以下方式注册和注销:

ini
代码解读
复制代码
objc 复制代码 [object addObserver:observer forKeyPath:keyPath options:options context:context]; [object removeObserver:observer forKeyPath:keyPath];
  • addObserver:forKeyPath: 方法用于注册观察者,观察对象的指定键路径。
  • removeObserver:forKeyPath: 方法用于注销观察者。

旧版 API 中,所有 KVO 观察者的管理都通过手动管理 addObserver 和 removeObserver 方法调用。如果忘记调用 removeObserver,则会导致内存泄漏或崩溃。手动管理观察者的生命周期也容易出错。

注意:

  • KVO 的实现底层是通过消息转发和动态方法解析实现的,因此会影响性能。
  • 由于没有现代 API 中的优化,一旦发生错误(如尝试访问已移除的观察者),程序容易崩溃。

2. 现代 KVO API

Apple 在 iOS 10 和 macOS 10.12 中引入了现代 KVO API,使用 NSKeyValueObservation 类来简化和安全地管理 KVO 观察者。现代 API 改进了管理观察者生命周期的方式,不需要手动调用 removeObserver。

使用方式:

typescript
代码解读
复制代码
objc 复制代码 @objc var observedObject = ObservableObject() var observation: NSKeyValueObservation? func startObserving() { observation = observedObject.observe(.someProperty, options: [.new, .old]) { object, change in // Handle property change } } func stopObserving() { observation = nil // Automatically removes observer }
  • 简化代码:现代 API 引入了 NSKeyValueObservation 类型的对象来自动管理观察者的生命周期,不再需要显式地调用 removeObserver。
  • 自动管理生命周期:当 NSKeyValueObservation 对象被销毁时,它会自动取消观察。无需担心忘记调用 removeObserver 导致的内存泄漏。
  • 强类型化:现代 API 是强类型的,采用键路径(Key Path)的形式,可以直接观察对象属性,而不像旧版 API 那样使用字符串形式的键路径,减少了出错的风险。
  • 异步回调和线程安全:现代 API 更好地支持异步回调,使得它在多线程环境下更为安全。

3. 现代 API 的优势

推荐使用现代 KVO API 的原因:

  • 自动管理观察者生命周期:无需手动移除观察者,避免了内存泄漏或崩溃的风险。
  • 简洁的代码和更好的可读性:现代 API 使用更简洁的语法,支持键路径表达式,减少了错误的发生。
  • 线程安全性:现代 API 提供更强的线程安全保证,适用于多线程环境。
  • 易于调试:通过 NSKeyValueObservation,可以轻松调试 KVO 的行为,避免传统的 KVO API 调用错误。

4. 比较

  • 旧版 API:通过 addObserver 和 removeObserver 手动管理观察者,需要小心处理观察者的添加和移除。缺乏强类型检查,易发生拼写错误和其他问题。
  • 现代 API:使用 NSKeyValueObservation 类来自动管理生命周期。API 更加简洁、安全,支持键路径表达式,能更方便地处理异步和线程安全问题。

总结

现代 KVO API 更加安全、简洁、易于使用,并且自动处理观察者的生命周期,因此推荐使用现代 API。它减少了手动管理和潜在错误的复杂性,提高了代码的可维护性和健壮性。

4o mini

您说:
  1. 如何解决 KVO 中可能发生的循环引用问题?
ChatGPT 说:

ChatGPT

在使用 KVO(Key-Value Observing)时,循环引用(retain cycle)是一个常见的问题。循环引用发生在观察者和被观察对象之间相互持有对方时,导致内存泄漏,因为它们不能被释放。解决 KVO 中的循环引用问题通常有以下几种方法。

1. 使用 weak 或 unowned 引用观察者

在添加 KVO 观察者时,最常见的循环引用问题是观察者(通常是视图控制器或其他对象)被 KVO 观察的对象强引用。为了避免这种情况,我们应该确保观察者对象不会被强引用,可以通过 weak 或 unowned 修饰符来避免循环引用。

示例:使用 weak 引用

swift
代码解读
复制代码
objc 复制代码 class MyClass { var observedObject: ObservableObject var observation: NSKeyValueObservation? init(observedObject: ObservableObject) { self.observedObject = observedObject // 使用 weak 防止循环引用 observation = observedObject.observe(.someProperty, options: [.new, .old]) { [weak self] object, change in guard let self = self else { return } // 处理属性变化 } } }

在这个示例中,[weak self] 确保 self(通常是视图控制器或其他对象)在闭包中是弱引用的,这样如果 self 被销毁,KVO 观察者不会持有它,避免了循环引用。

示例:使用 unowned 引用

如果你确信观察者在 KVO 的生命周期内始终存在,并且观察者不会被提前释放,可以使用 unowned self。unowned 引用与 weak 类似,但不同的是,如果 self 被销毁,unowned self 会导致崩溃,而 weak 引用则会变为 nil。

php
代码解读
复制代码
objc 复制代码 observation = observedObject.observe(.someProperty, options: [.new, .old]) { [unowned self] object, change in // 处理属性变化 }

使用 unowned 可以减少某些情况下的内存开销,特别是当你确定对象生命周期的一致性时。

2. 移除观察者

在旧版 KVO API 中,循环引用的常见原因是未正确移除观察者。如果你在使用旧版 KVO API(如 addObserver:forKeyPath:)时忘记移除观察者,观察者将会持有被观察对象,从而导致循环引用。因此,确保在适当的时候(例如对象销毁时)手动移除观察者是至关重要的。

php
代码解读
复制代码
objc 复制代码 // 在对象销毁时移除观察者 deinit { observedObject.removeObserver(self, forKeyPath: "someProperty") }

在现代 KVO API 中(使用 NSKeyValueObservation),观察者的生命周期由 NSKeyValueObservation 自动管理,无需手动移除,但仍然要注意正确释放引用,避免手动管理引发的循环引用。

3. 避免在闭包中强引用自身

当使用闭包(block)作为观察者时,尤其是在处理 KVO 时,需要小心避免闭包内部强引用自身。比如,闭包内如果直接使用 self,可能会导致循环引用。使用 weak 或 unowned 引用来避免闭包强引用外部对象。

php
代码解读
复制代码
objc 复制代码 observation = observedObject.observe(.someProperty, options: [.new, .old]) { [weak self] object, change in guard let self = self else { return } // 处理变化 }

这里的 weak self 确保如果 self 被销毁,闭包不会继续强引用它,避免了内存泄漏。

4. 使用 NSKeyValueObservation 的生命周期管理

现代 KVO API(NSKeyValueObservation)会自动管理观察者的生命周期。当 NSKeyValueObservation 对象被销毁时,KVO 观察者会自动从观察列表中移除,从而避免了传统 KVO API 中的循环引用问题。

你可以通过确保 NSKeyValueObservation 对象的生命周期结束来避免循环引用:

typescript
代码解读
复制代码
objc 复制代码 var observation: NSKeyValueObservation? func observeObject() { observation = observedObject.observe(.someProperty, options: [.new, .old]) { [weak self] object, change in // 处理属性变化 } } // 当观察结束时,observation 将被销毁,自动移除观察者

5. 清理和解除引用

确保在适当的地方清理引用,尤其是在观察结束或对象销毁时。对于 KVO 观察者,应该确保移除任何不再需要的引用,以避免不必要的内存占用。

总结

  • 使用 weak 或 unowned 引用:确保观察者对象不会被强引用,避免循环引用。
  • 正确移除观察者:如果使用旧版 KVO API,确保在不再需要观察时移除观察者。
  • 避免在闭包中强引用 self:使用 weak 或 unowned 引用避免闭包导致的循环引用。
  • 利用现代 KVO API(NSKeyValueObservation)自动管理观察者的生命周期:避免手动管理观察者,减少内存泄漏的风险。

这些方法可以有效地避免 KVO 中的循环引用问题,保证内存的正确管理和程序的稳定运行。

###7.kvc设置值可以触发KVO 因为KVC赋值的方法 setValueforKey 底层会调用setter方法,所以能触发 image.png image.png

注:本文转载自juejin.cn的小亲亲亲0的文章"https://juejin.cn/post/7454791207224901686"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

141
iOS
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top