IOS技术-KVO

KVO的底层实现

KVO(Key-Value Observing)是 iOS 和 macOS 中用于观察对象属性变化的机制。它允许一个对象观察另一个对象的特定属性,并在该属性发生变化时收到通知。KVO 的底层实现涉及多个方面,以下是对其实现原理的详细解释:

1. 动态特性

KVO 基于 Objective-C 的动态特性,使用运行时机制来实现。它依赖于以下几个核心概念:

  • 动态消息发送:KVO 通过动态消息发送来处理属性的访问和修改。这意味着 KVO 可以在运行时查找和调用 getter 和 setter 方法。

2. 注册观察者

  • 当一个对象注册为另一个对象的观察者时,KVO 会将观察者与被观察者的属性关联起来。这通常是通过调用 addObserver:forKeyPath:options:context: 方法实现的。

3. 属性的 KVO 兼容性

  • KVO 仅适用于那些遵循 KVC(Key-Value Coding)规则的属性。被观察的属性必须是 KVC 兼容的,即它必须具有对应的 getter 和 setter 方法。

  • KVO 对于属性的观察需要使用 @objc dynamic 关键字声明属性,以确保运行时能够拦截访问。

4. 实现细节

  • 内部存储:KVO 会在被观察者的实例中维护一个观察者列表,存储每个观察者的信息,包括观察者对象、观察的键路径以及相关的选项。

  • 重写 getter 和 setter:KVO 会在运行时为被观察的对象的属性动态生成子类,重写其 getter 和 setter 方法。当属性的值被修改时,重写的 setter 会在设置新值后通知所有注册的观察者。

5. 通知机制

  • 发送通知:当属性的值发生变化时,重写的 setter 会调用 willChangeValueForKey:didChangeValueForKey: 方法,分别在值变化前后发送通知。

  • 观察者接收通知:观察者在注册时指定一个回调方法(通常是 observeValueForKeyPath:ofObject:change:context:),当属性变化时,该方法会被调用,传递包括新值和旧值在内的相关信息。

6. 内存管理

  • 自动移除观察者:KVO 需要在观察者对象被销毁时自动移除观察者,以避免内存泄漏和崩溃。通常在观察者的 dealloc 方法中调用 removeObserver:forKeyPath:

7. 示例

以下是一个简单的 KVO 示例,演示如何使用 KVO 观察属性变化:

@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@end

@implementation Person
@end

// 观察者类
@interface Observer : NSObject
@end

@implementation Observer

- (void)observeProperty {
    Person *person = [[Person alloc] init];
    
    // 添加 KVO 观察者
    [person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    
    // 修改属性
    person.name = @"Alice"; // 触发 KVO 通知
}

// KVO 通知的回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSKeyValueChangeKey,id> *)change
                       context:(void *)context {
    if ([keyPath isEqualToString:@"name"]) {
        NSString *newName = change[NSKeyValueChangeNewKey];
        NSLog(@"Name changed to: %@", newName);
    }
}

- (void)dealloc {
    // 移除观察者
    [self removeObserver:self forKeyPath:@"name"];
}

@end

总结

KVO 的底层实现依赖于 Objective-C 的动态特性,通过重写 getter 和 setter 方法、维护观察者列表和发送通知来实现属性变化的观察。了解 KVO 的实现原理有助于开发者在使用时更加高效,同时避免潜在的内存管理问题。

KVO与Notification机制相比,有什么优缺点?

KVO(Key-Value Observing)和 Notification(通知)机制是 iOS 和 macOS 开发中常用的两种观察模式,各自有其特点和适用场景。以下是它们的优缺点比较:

KVO(Key-Value Observing)

优点

  1. 自动化管理

    • KVO 自动处理观察者与被观察者之间的关系,提供了一种简洁的方式来观察属性的变化。
  2. 直接性

    • KVO 能够直接观察对象的属性变化,提供了对属性值的即时访问。
  3. 数据一致性

    • KVO 可以确保观察者始终获得最新的属性值,适合需要频繁更新的场景。
  4. 上下文信息

    • KVO 支持通过 context 参数传递信息,使得观察者可以更灵活地处理不同的观察情境。

缺点

  1. 复杂性

    • KVO 的实现相对复杂,涉及动态消息发送和运行时机制,容易出错。
  2. 内存管理

    • 需要手动移除观察者,容易导致内存泄漏或崩溃(特别是在观察者未能及时移除的情况下)。
  3. 性能开销

    • KVO 的性能开销相对较大,因为它依赖于运行时的动态特性,尤其是在观察多个属性时。
  4. 类型限制

    • KVO 仅适用于 KVC 兼容的属性,必须具有对应的 getter 和 setter 方法。

Notification(通知)

优点

  1. 松耦合

    • 通知机制允许发送者和接收者之间保持松耦合,接收者不需要知道发送者的具体实现。
  2. 灵活性

    • 可以发送任意类型的通知,适用于多个对象之间的事件通信。
  3. 易于使用

    • 使用简单,注册和移除观察者都很直观,适合简单的事件传递场景。
  4. 全局性

    • 通知可以在应用的任何地方发送和接收,适合广播消息的场景。

缺点

  1. 缺乏直接性

    • 通知机制不会直接提供被通知对象的状态,需要从通知的 userInfo 中提取信息。
  2. 数据一致性

    • 观察者收到通知时,无法确保数据的一致性,特别是在多个通知接收者的情况下。
  3. 内存管理

    • 同样需要手动移除观察者,若忘记移除会导致内存泄漏。
  4. 性能开销

    • 在大量通知的情况下,可能会带来性能开销,尤其是在有多个观察者时。

总结

  • KVO 适合需要观察对象属性变化、确保数据一致性的场景,尤其是在涉及复杂数据模型时。
  • Notification 适用于松耦合的事件传播,特别是当需要在多个组件之间传递事件时。

选择使用 KVO 还是 Notification 取决于具体的应用场景、需求和设计架构。在设计时,理解它们的优缺点可以帮助做出更合适的选择。

如果属性不是KVC兼容的会发生什么?

在使用 KVO(Key-Value Observing)时,如果属性不是 KVC(Key-Value Coding)兼容的,会发生以下几种情况:

1. 无法注册观察者

  • 如果你尝试对一个非 KVC 兼容的属性注册 KVO 观察者,系统会抛出异常。KVO 需要属性具有相应的 getter 和 setter 方法,以便能够正确地访问和修改属性值。

2. 运行时错误

  • 如果你试图使用 KVO 观察一个没有 KVC 兼容方法的属性,通常会在运行时遇到错误,例如 NSInvalidArgumentException。这表明 KVO 无法找到对应的 getter 或 setter 方法。

3. 属性类型限制

  • KVO 仅支持具有 KVC 兼容性的属性类型。比如,NSString、NSNumber、NSArray 等都是 KVC 兼容的,但如果属性是某些自定义类型或不符合 KVC 的类型,KVO 将无法正常工作。

4. 动态特性缺失

  • KVO 的核心依赖于动态特性。如果你使用的属性未声明为 @objc dynamic,KVO 将无法拦截对该属性的访问,也就无法进行观察。

5. 示例

以下是一个简单的示例,展示对非 KVC 兼容属性的使用及其结果:

@interface Person : NSObject
@property (nonatomic, strong) NSString *name; // KVC 兼容
@property (nonatomic) int age; // 非 KVC 兼容
@end

@implementation Person
@end

Person *person = [[Person alloc] init];

// 注册观察者
[person addObserver:self forKeyPath:@"name" options:0 context:nil]; // 正常

[person addObserver:self forKeyPath:@"age" options:0 context:nil]; // 可能导致异常

在这个示例中,name 属性是 KVC 兼容的,而 age 属性可能会导致错误,尤其是在没有对应的 getter/setter 方法时。

总结

如果属性不是 KVC 兼容的,KVO 将无法正常工作,可能导致运行时错误或异常。为了确保 KVO 的正确性,必须确保所有被观察的属性都符合 KVC 的要求,包括具有合适的访问方法和类型。

自定义类型如何才能满足KVC兼容性要求?

要使自定义类型满足 KVC(Key-Value Coding)兼容性要求,需要遵循一些特定的规则和最佳实践。以下是确保自定义对象能够被 KVC 兼容的步骤:

1. 使用 Objective-C 类

确保你的自定义类型是一个 Objective-C 类。KVC 主要依赖于 Objective-C 的运行时特性。

2. 提供 getter 和 setter 方法

自定义类应为每个需要被 KVC 访问的属性提供对应的 getter 和 setter 方法。这些方法必须遵循特定的命名约定:

  • Getter 方法:通常以 propertyName 命名。
  • Setter 方法:通常以 setPropertyName: 命名。

示例:

@interface CustomObject : NSObject
@property (nonatomic, strong) NSString *title; // KVC 兼容
@property (nonatomic) NSInteger count; // KVC 兼容
@end

@implementation CustomObject
// KVC 自动合成 getter 和 setter
@end

3. 使用 @objc dynamic 修饰符

如果你的自定义类型是 Swift 类,并且你打算使用 KVO,确保在类和需要被观察的属性上使用 @objc dynamic 修饰符。这使得属性能够被 KVO 观察。

示例(Swift):

@objcMembers
class CustomObject: NSObject {
    var title: String = ""
    dynamic var count: Int = 0
}

4. 遵循 NSKeyValueCoding 协议

虽然直接实现 KVC 的要求通常不需要显式遵循 NSKeyValueCoding 协议,但确保你的类能够处理 KVC 方法(如 valueForKey:setValue:forKey:)是很重要的。

5. 支持嵌套属性

如果自定义类型中有嵌套属性,确保这些嵌套属性也符合 KVC 兼容性要求。例如,如果你有一个包含其他自定义对象的数组或字典,确保这些对象也满足 KVC 的要求。

6. 使用可选类型

在 Swift 中,确保使用可选类型(如 String?)时,能够正确处理 nil 值。这有助于与 KVC 的兼容性。

7. 遵循 KVC 规则

  • 避免使用 privatefileprivate 访问修饰符:KVC 需要能够访问属性,因此属性应为 publicinternal
  • 使用 KVC 兼容的类型:确保属性使用的类型是 KVC 兼容的(如 NSString、NSNumber、NSArray 等)。

总结

通过确保自定义类型是 Objective-C 类或带有 @objc 修饰符的 Swift 类,提供合适的 getter 和 setter 方法,以及遵循 KVC 的其他要求,可以使自定义类型满足 KVC 兼容性要求。这将允许你在使用 KVO 和其他 KVC 功能时,正确地访问和操作对象的属性。

Keypath

在 iOS 开发中,KeyPath、KVC(Key-Value Coding)和 KVO(Key-Value Observing)是一些重要的概念,特别是在处理对象属性和观察变化时。以下是对它们的详细解释:

1. KeyPath

KeyPath 是一种字符串表示法,用于访问对象的属性。它可以用来导航到对象的嵌套属性。KeyPath 在 Swift 和 Objective-C 中都有不同的实现方式。

示例

struct Person {
    var name: String
    var age: Int
}

let keyPath = \Person.name

在这个示例中,keyPath 是一个指向 Person 对象 name 属性的 KeyPath。

  • KeyPath 提供了一种通过属性路径访问对象属性的方法
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容