iOS - KVO 键值观察

1、KVO

  • KVO 是 Key-Value Observing 的简写,是键值观察的意思,属于 runtime 方法。Key Value Observing 顾名思义就是一种 observer 模式用于监听属性变量值的变化,也是运行时的方法,当实例变量改变时,系统会自动采取一些动作。KVO 跟 NSNotification 有很多相似的地方,用 addObserver:forKeyPath:options:context: 去 start observer, 用 removeObserver:forKeyPath:context 去 stop observer, 回调就是 observeValueForKeyPath:ofObject:change:context:。
  • 对于 KVO 来说,我们要做的只是简单 update 我们的 property 的数据,不需要像 NSNotificationCenter 那样关心是否有人在监听你的请求,如果没有人监听该怎么办,所有 addObserver, removeObserver, callback 都是想要监听的你的 property 的 class 做的事情。曾经做个项目,用 NSNotificationCenter post Notification 在一个 network callback 里面,可是这时候因为最早的 addObserver 的 class 被释放了,接着生成的 addObserver 的 class, 就接受到了上一个 observer 该监听的事件,所以造成了错误,那时候的解决方案是为 addObserve key 做 unique,不会 2 次 addObserver 的 key 是相同的,但是有了 KVO, 我们同样可以用 KVO 来完成,当 addOberver 的的 object remove 的时候,就不会有这样的 callback 被调用了。
  • KVO 给我们提供了更少的代码,和 NSNotification 比好处,不需要修改被观察的 class, 永远都是观察你的人做事情。 但是 KVO 也有些毛病:
    • 1、如果没有 observer 监听 keyPath, removeObsever:forKeyPath:context: 这个 keyPath, 就会 crash(崩溃), 不像 NSNotificationCenter removeObserver。
    • 2、对代码你很难发现谁监听你的 property 的改动,查找起来比较麻烦。
    • 3、对于一个复杂和相关性很高的 class,最好还是不要用 KVO, 就用 delegate 或者 notification 的方式比较简洁。
  • KVO 使用分三步:
    • 1、注册成为观察者。
    • 2、观察者定义 KVO 的回调。
    • 3、移除观察者。
  • KVO 使用注意:
    • KVO 是同步的,一旦对象的属性发生变化,只有用同步的方式,才能保证所有观察者的方法能够执行完成。KVO 监听方法中,不要有太耗时的操作。
    • KVO 的方法调用,是在对应的线程中执行的。在子线程修改观察属性时,观察者回调方法将在子线程中执行。
    • 在多个线程同时修改一个观察属性的时候,KVO 监听方法中会存在资源抢夺的问题,需要使用互斥锁。如果涉及到多线程,KVO 要特别小心,通常 KVO 只是做一些简单的观察和处理,千万不要搞复杂了,KVO的监听代码,一定要简单。
    • 一定要删除观察者,如果不删除观察者,释放对象,会直接崩溃。An instance 0x7fd340ebc400 of class KvoClass was deallocated while key value observers were still registered with it.
  • 在 Swift 中使用 KVO 的前提条件:
    • 1、观察者和被观察者都必须是 NSObject 的子类,因为 OC 中 KVO 的实现基于 KVC 和 runtime 机制,只有是 NSObject 的子类才能利用这些特性;
    • 2、观察的属性需要使用 dynamic 关键字修饰,表示该属性的存取都由 runtime 在运行时来决定,由于 Swift 基于效率的考量默认禁止了动态派发机制,因此要加上该修饰符来开启动态派发。

2、KVO 的使用

  • Objective-C

        // KvoClass.h
    
            @interface KvoClass : NSObject
    
            @property(nonatomic, copy) NSString *name;
    
            @end
    
        // ViewController.m
    
            @property(nonatomic, retain) KvoClass *kvoObject;
    
            _kvoObject = [[KvoClass alloc] init];
  • Swift
        // KvoClass.swift
    
            class KvoClass: NSObject {
    
                dynamic var name:String!
            }
    
        // ViewController.swift
    
            var nameContext = "nameChange"
    
            var kvoObject = KvoClass()

2.1 KVO 添加

    - (void)addObserver:(NSObject *)observer
             forKeyPath:(NSString *)keyPath
                options:(NSKeyValueObservingOptions)options
                context:(nullable void *)context;

    public func addObserver(observer: NSObject,
                  forKeyPath keyPath: String,
                             options: NSKeyValueObservingOptions,
                             context: UnsafeMutablePointer<Void>)

    参数说明:
       第一个参数 observer 是观察的类;
       第二个参数 keyPath 是被观察的类中被观察的属性;
       第三个参数 options 是观察选项;
       第四个参数 context 是传递的上下文内容。
  • Objective-C

        // 添加观察者
        [_kvoObject addObserver:self
                     forKeyPath:@"name"
                        options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
                        context:@"nameChange"];
    
        // 改变被观察的键对应的值
        _kvoObject.name = @"xiao bai";
        sleep(2);
        _kvoObject.name = @"xiao hei";
  • Swift
        // 添加观察者
        kvoObject.addObserver(self, forKeyPath:"name", options:[.New, .Old], context:&nameContext)
    
        // 改变被观察的键对应的值
        kvoObject.name = "xiao bai"
        sleep(2)
        kvoObject.name = "xiao hei"

2.2 KVO 回调

    - (void)observeValueForKeyPath:(nullable NSString *)keyPath
                          ofObject:(nullable id)object
                            change:(nullable NSDictionary<NSString*, id> *)change
                           context:(nullable void *)context;

    public func observeValueForKeyPath(keyPath: String?,
                               ofObject object: AnyObject?,
                                        change: [String : AnyObject]?,
                                       context: UnsafeMutablePointer<Void>)

    参数说明:
        keyPath:监控的 key;
        object:被监控的对象的基本属性;
        change:被监控的对象的 key 对应的 value 值的变化(kind:类型,new:变化后的值,old:变化前的值。
  • Objective-C

        // 系统自带方法
        - (void)observeValueForKeyPath:(NSString *)keyPath
                              ofObject:(id)object
                                change:(NSDictionary *)change
                               context:(void *)context {
    
            if (context == @"nameChange") {
    
                NSLog(@"name 值被改变 kind = %@, oldValue = %@, newValue = %@",
                             change[@"kind"], change[@"old"], change[@"new"]);
    
            } else {
                [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
            }
        }
  • Swift
        override func observeValueForKeyPath(keyPath: String?,
                                     ofObject object: AnyObject?,
                                              change: [String : AnyObject]?,
                                             context: UnsafeMutablePointer<Void>) {
    
            if context == &nameContext {
    
                print("name 值被改变 kind = \(change![NSKeyValueChangeKindKey]),
                                 oldValue = \(change![NSKeyValueChangeOldKey]),
                                 newValue = \(change![NSKeyValueChangeNewKey])")
            }
            else {
                super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
            }
        }

2.3 KVO 移除

  • 在实际工作中需要在合适的时候移除观察者身份。

        NS_AVAILABLE(10_7, 5_0)
        - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
        - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
    
        public func removeObserver(observer: NSObject, forKeyPath keyPath: String, context: UnsafeMutablePointer<Void>)
        public func removeObserver(observer: NSObject, forKeyPath keyPath: String)
    
        参数说明:
            第一个参数 observer 是观察的类;
            第二个参数 keyPath 是被观察的类中被观察的属性;
            第三个参数 context 是传递的上下文内容。
  • Objective-C
        - (void)dealloc {
    
            // 移除观察者
            [_kvoObject removeObserver:self forKeyPath:@"name" context:@"nameChange"];
        }
  • Swift
        deinit {
    
            // 移除观察者
            kvoObject.removeObserver(self, forKeyPath:"name", context:&nameContext)
        }
时间: 2024-10-26 03:14:05

iOS - KVO 键值观察的相关文章

obj-c编程17:键值观察(KVO)

    说完了前面一篇KVC,不能不说说它的应用KVO(Key-Value Observing)喽.KVO类似于ruby里的hook功能,就是当一个对象属性发生变化时,观察者可以跟踪变化,进而观察或是修正这个变化,这是通过回调观察者注册的回调函数来完成的.要使用键值观察,必须满足3个条件: 1 被观察对象必须对所观察属性使用符合KVC标准的存取器方法: 2 观察者必须实现接受通知的方法(回调方法):-observeValue:forKeyPath:ofObject:change:context:

iOS中 KVO 键值观察者

KVO Key-Value-Obsever 键值观察者 1.首先要有一个观察者,此时被观察者是自己找一个观察者观察自己的key值对应的value值有没有改变,如果改变了就可以做一些响应的操作 创建一个被观察者: self.person = [[Person alloc]init]; 2.给被观察者注册观察者 NSKeyValueObservingOptionNew 当重新给被观察者的key值赋值时,触发监听方法,此时打印的 change 永远都是最新的值 存放的是value值 NSKeyValu

iOS - KVC 键值编码

1.KVC KVC 是 Key-Value Coding 的简写,是键值编码的意思,属于 runtime 方法.Key Value Coding 是 cocoa 的一个标准组成部分,是间接给对象属性设置数值的方法,它能让我们可以通过 name(key) 的方式访问属性变量, 不必调用明确的属性访问方法, 如我们有个属性变量叫做 foo, 我们可以 foo 直接访问它,同样我们也可以用 KVC 来完成 [Object valueForKey:@"foo"], 这样做主要的好处就是来减少我

基于键值的观察者模式编程

键值观察提供了一个机制,允许当其他对象的特定属性被修改时通知观察者对 象.这一机制在应用程序的模型和控制器之间的通讯方面尤其有用.通常情况下 ,控制器对象观察模型对象的属性,而视图对象通过控制器观察模型对象的属性 .此外,一个模型对象也可以观察其他模型对象. 要接收一个属性的键值观察通知,需要: 被观察对象调用addObserver:forKeyPath:options:context:方法注册观察者 对象: 观察类必须实现observeValueForKeyPath:ofObject:chan

php数组中键值对怎么理解

问题描述 php数组中键值对怎么理解 php数组中的键值对不是理解很透,是否说的是可以自定义一个下标,让后创建一个新的键值对,让后经常出现foreach($a as $key=>$b){...}.里面的$key作用是什么 解决方案 简单说吧.上面foreach就是遍历数组$a的每个元素,并把每个元素的键.值分别赋给 $key.$b,便于在循环主体中使用. 下面 1.php中array当纯粹的数组用,$key就是数组元素的下标, array('a1','a2'); 等价于 array(0 =>'

【iOS7的一些总结】7、键值观察者模式KVO

1.概念: 所谓键值观察者,是一个对象获取其他对象特定属性变化的通知机制.看上去KVO同通知机制类似,而不同点在于KVO没有提供一个所谓的通知中心为所有的观察者提供变化通知,而是当变化发生时,通知直接送达正在观察的对象. 2.键值观察者模式的实现: (1)注册观察者: 观察对象发送消息到被观察对象,以建立二者的观察关系: - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyVa

iOS网络编程入门:iCloud键值数据存储编程实例

iCloud键值数据存储设计 iCloud键值数据存储编程实例,画面中有两个开关控件,左图是设备1点击"设置iCloud数据"按钮,将控件状态保存到iCloud服务器.右图是设备2画面,过几秒钟后设备2收到变更通知. 配置Xcode工程 使用Xcode创建一个iOS工程,工程创建好之后,选择TAGETS→MyNotes→Summary→Entitlements,我们可以在这里配置授权信息. 然后我们还需要应用设置代码签名标识,代码签名标识需要选择这个配置概要文件的.选择TAGETS→M

《iOS组件与框架——iOS SDK高级特性剖析》——第8章,第8.6节键值存储同步

8.6 键值存储同步 iOS组件与框架--iOS SDK高级特性剖析 iCloud还支持键值存储同步.这类似于在NSMutableDictionary或NSUserDefaults中存储信息,将一个与对象值相关联的键用于存储和检索:iCloud键值存储的不同之处在于,将自动在设备之间同步键和值.本书编写期间,iCloud为每个应用提供1MB的键值存储空间,最多可存储1024个键值对,因此键值存储机制只适合用于存储少量信息. 本章的示例应用利用iCloud键值存储来跟踪最后修改的备忘录.详细视图控

iOS网络编程-iCloud键值数据存储编程实例

iCloud键值数据存储设计 iCloud键值数据存储编程实例,画面中有两个开关控件,左图是设备1点击"设置iCloud数据"按钮,将控件状态保存到iCloud服务器.右图是设备2画面,过几秒钟后设备2收到变更通知.     配置Xcode工程 使用Xcode创建一个iOS工程,工程创建好之后,选择TAGETS→MyNotes→Summary→Entitlements,我们可以在这里配置授权信息. 然后我们还需要应用设置代码签名标识,代码签名标识需要选择这个配置概要文件的.选择TAGE