iOS addObserver参数如何正确使用?

99ANYc3cd6
预计阅读时长 24 分钟
位置: 首页 参数 正文

这个方法是 KVO (Key-Value Observing) 机制的核心,用于为某个对象注册一个观察者,以便在指定属性的值发生变化时收到通知。

ios addobserver 参数
(图片来源网络,侵删)

方法签名

// Objective-C
- (void)addObserver:(NSObject *)observer
         forKeyPath:(NSString *)keyPath
            options:(NSKeyValueObservingOptions)options
            context:(nullable void *)context;
// Swift (通过 KVO 机制,语法更简洁)
// 通常使用 @objcMembers 或 dynamic 修饰,或者继承自 NSObject
// 注册观察
override func observeValue(forKeyPath keyPath: String?,
                          of object: Any?,
                          change: [NSKeyValueChangeKey : Any]?,
                          context: UnsafeMutableRawPointer?)
// 移除观察
deinit {
    // 通常在这里移除观察者
}

参数详解

下面我们逐一分析每个参数的作用和可选值。

observer: NSObject (观察者)

  • 类型: NSObject (或其子类)
  • 作用: 指定一个对象,这个对象将负责接收属性变化的通知,当被观察对象的 keyPath 对应的值发生改变时,observer 对象的 observeValueForKeyPath:ofObject:change:context: 方法会被调用。
  • 关键点:
    • 观察者必须遵守 NSKeyValueObserving 协议,虽然这个协议是隐式的(任何 NSObject 都默认遵循),但你的类需要实现 observeValueForKeyPath:ofObject:change:context: 方法来处理通知。
    • 观察者和被观察的对象可以是同一个对象,但通常不推荐,这可能会导致逻辑混乱。
    • 移除观察者: 当观察者对象(通常是 ViewController)被销毁时,必须调用 removeObserver:forKeyPath: 来移除观察,否则,当被观察对象发生变化时,会向一个已经不存在的对象发送消息,导致程序崩溃,最佳实践是在观察者的 deinit 方法中移除所有观察。

keyPath: NSString (被观察的属性路径)

  • 类型: NSString
  • 作用: 指定一个点分隔的路径字符串,用于标识要观察的属性,这个路径可以指向一个对象本身的属性,也可以指向其嵌套对象的属性。
  • 关键点:
    • 简单属性: 如果要观察对象自身的 name 属性,keyPath @"name"
    • KVC 兼容: keyPath 的语法与 Key-Value Coding (KVC) 完全一致。
    • 点语法: 对于嵌套属性,使用点语法,要观察一个 Person 对象的 address 属性中的 city 属性,keyPath @"address.city"
    • 必须是属性: keyPath 必须对应一个真实的、符合 KVO 规范的属性,直接观察实例变量(_ivar)是无效的。

options: NSKeyValueObservingOptions (通知选项)

  • 类型: NSKeyValueObservingOptions (这是一个 NS_OPTIONS 枚举,可以组合使用)

  • 作用: 定义在属性值发生变化时,传递给 observeValueForKeyPath... 方法的 change 字典中包含哪些额外信息,它决定了你何时收到通知以及通知的详细程度。

  • 可选值:

    ios addobserver 参数
    (图片来源网络,侵删)
    • NSKeyValueObservingOptions.new (0x01): 在 change 字典中包含变化后(新)的值,键为 NSKeyValueChangeNewKey
    • NSKeyValueObservingOptions.old (0x02): 在 change 字典中包含变化前(旧)的值,键为 NSKeyValueChangeOldKey
    • NSKeyValueObservingOptions.initial (0x01): 在观察建立之后,立即发送一次通知,这次通知的 change 字典中会包含属性的初始值,这对于确保在对象创建后就能立即获取到属性的初始状态非常有用。
    • NSKeyValueObservingOptions.prior (0x02): 在值改变之前改变之后都发送通知,改变前的通知中,change 字典里的 NSKeyValueChangeNotificationIsPriorKey 键对应的值为 @YES
  • 常用组合:

    • [.new, .initial]: 这是最常用的组合之一,它确保你既能收到属性初始值的通知,也能在每次属性变化后收到新值。
    • .new: 只关心变化后的新值。
    • .old: 只关心变化前的旧值(在移除某个对象前,需要知道它原来的值)。
    • .prior: 用于在值改变前执行一些操作,比如取消动画或更新 UI 状态。

context: void * (上下文)

  • 类型: void * (在 Swift 中是 UnsafeMutableRawPointer?)
  • 作用: 一个可选的、不透明的指针,用作传递给观察者的自定义数据,它主要用于解决一个关键问题:观察者冲突
  • 关键点:
    • 解决冲突: 当一个对象(ViewController)同时观察了多个不同对象的同一个 keyPath 时,在 observeValueForKeyPath... 方法中,你很难判断这个变化是来自哪个被观察对象的,通过为每个 addObserver 调用传入不同的 context,你就可以在回调方法中通过 context 参数来区分不同的通知来源。
    • 避免使用 keyPath 判断: 强烈建议不要在 observeValueForKeyPath... 中通过 if ([keyPath isEqualToString:@"..."]) 来分支处理,因为如果 keyPath 相同但 context 不同,这种判断就会失效。context 是更可靠、更优雅的解决方案。
    • 如何使用:
      1. 定义一个唯一的指针作为上下文,通常使用 static void * const MyContext = &MyContext; 这样的方式。
      2. addObserver 时传入这个指针。
      3. observeValueForKeyPath... 方法中,检查传入的 context 是否与你传入的一致,然后执行相应的逻辑。
    • 传递 nil: 如果你不需要解决冲突,或者只观察一个对象,可以安全地传递 nil

代码示例 (Swift)

下面是一个完整的 Swift 示例,展示了所有参数的用法。

import Foundation
// 1. 定义被观察的模型
class Person: NSObject {
    @objc dynamic var name: String = "Initial Name"
    @objc dynamic var age: Int = 0
    // 为了演示嵌套 keyPath
    @objc dynamic var address: String = "Unknown"
}
// 2. 定义观察者
class MyViewController: NSObject {
    // 定义两个不同的 context,用于区分观察来源
    private var nameObservationContext = UnsafeMutableRawPointer.allocate(byteCount: 1, alignment: 1)
    private var ageObservationContext = UnsafeMutableRawPointer.allocate(byteCount: 1, alignment: 1)
    var person = Person()
    override init() {
        super.init()
        setupKVO()
    }
    deinit {
        print("MyViewController is being deallocated. Removing observers...")
        // 3. 在 deinit 中移除观察者,防止内存泄漏和崩溃
        person.removeObserver(self, forKeyPath: #keyPath(Person.name), context: nameObservationContext)
        person.removeObserver(self, forKeyPath: #keyPath(Person.age), context: ageObservationContext)
        // 释放内存
        nameObservationContext.deallocate()
        ageObservationContext.deallocate()
    }
    func setupKVO() {
        // 观察 name 属性
        // 参数解析:
        // observer: self (当前 ViewController)
        // keyPath: #keyPath(Person.name) (编译器安全的属性名)
        // options: [.new, .initial] (关心新值和初始值)
        // context: nameObservationContext (自定义上下文,用于区分)
        person.addObserver(self, forKeyPath: #keyPath(Person.name), options: [.new, .initial], context: nameObservationContext)
        // 观察 age 属性
        person.addObserver(self, forKeyPath: #keyPath(Person.age), options: [.new], context: ageObservationContext)
    }
    // 4. 实现 KVO 回调方法
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        // 5. 使用 context 来判断通知来源
        if context == nameObservationContext {
            print("--- Name Observation ---")
            // 可以安全地断言 change 字典中存在新值
            if let newName = change?[.newKey] as? String {
                print("Person's name changed to: \(newName)")
            }
        } else if context == ageObservationContext {
            print("--- Age Observation ---")
            if let newAge = change?[.newKey] as? Int {
                print("Person's age changed to: \(newAge)")
            }
        } else {
            // context 不匹配,调用父类的实现,避免潜在的 bug
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        }
    }
}
// --- 测试 ---
let viewController = MyViewController()
// 模拟属性变化
// 这会触发 KVO 通知
viewController.person.name = "Alice"
viewController.person.age = 30
// 再次变化
viewController.person.name = "Bob"
viewController.person.age = 31
参数 类型 作用 最佳实践
observer NSObject 接收通知的对象。 必须实现 observeValueForKeyPath...,并在 deinit 中移除观察。
keyPath NSString 要观察的属性路径。 使用点语法支持嵌套属性,确保是有效的 KVC 属性路径。
options NSKeyValueObservingOptions 定义通知内容和时机。 根据需要组合使用 .new, .old, .initial, .prior.new.initial 是最常用的组合。
context void * 用于区分不同通知来源的指针。 强烈推荐使用,以避免 keyPath 冲突,使代码更健壮。

理解这四个参数是掌握 KVO 的关键,现代 Swift 更推荐使用 Combine 框架或基于闭包的第三方库(如 RxSwift, Bond)来进行响应式编程,因为它们更安全、更易于使用,但在处理 Objective-C 代码或特定系统框架时,KVO 仍然是一个不可或缺的工具。

ios addobserver 参数
(图片来源网络,侵删)
-- 展开阅读全文 --
头像
黑莓Priv参数有哪些亮点?
« 上一篇 12-07
InstallShield函数参数如何正确传递与使用?
下一篇 » 12-07

相关文章

取消
微信二维码
支付宝二维码

最近发表

标签列表

目录[+]