当前位置: 首页 > news >正文

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)
        }

转载于:https://www.cnblogs.com/QianChia/p/5771074.html

相关文章:

  • Power BI官方视频(2) Power BI嵌入到应用中的3种方法
  • 模板整理
  • MySQL---数据库从入门走向大神系列(十七)-JavaWeb分页技术实例演示2
  • TYVJ P1067 合唱队形 Label:上升子序列?
  • 使用有源匹配电路改善宽带全差分放大器的噪声性能
  • 关于JavaScript初级的知识点一(持续更新 )
  • Android - 看似内存泄漏,实则不是,记一次内存泄漏的案例分析
  • Linux下创建软RAID5和RAID10实战
  • 【原创】遨游springmvc之HandlerMethodReturnValueHandler
  • css 样式表分类总结
  • Babel6.x 转换ES6
  • SharpGL学习笔记(五) 视口变换
  • win2012配置
  • shell运算(加、减、乘、除)
  • 配置 linux-bridge mechanism driver - 每天5分钟玩转 OpenStack(77)
  • 03Go 类型总结
  • Android Volley源码解析
  • C++入门教程(10):for 语句
  • ECS应用管理最佳实践
  • export和import的用法总结
  • javascript数组去重/查找/插入/删除
  • Laravel 实践之路: 数据库迁移与数据填充
  • miniui datagrid 的客户端分页解决方案 - CS结合
  • Mithril.js 入门介绍
  • Vue UI框架库开发介绍
  • 容器化应用: 在阿里云搭建多节点 Openshift 集群
  • 如何选择开源的机器学习框架?
  • 用Node EJS写一个爬虫脚本每天定时给心爱的她发一封暖心邮件
  • 函数计算新功能-----支持C#函数
  • 数据库巡检项
  • ​secrets --- 生成管理密码的安全随机数​
  • ​卜东波研究员:高观点下的少儿计算思维
  • # Panda3d 碰撞检测系统介绍
  • #DBA杂记1
  • #ifdef 的技巧用法
  • #NOIP 2014#Day.2 T3 解方程
  • #pragma 指令
  • (1)(1.9) MSP (version 4.2)
  • (39)STM32——FLASH闪存
  • (7)STL算法之交换赋值
  • (env: Windows,mp,1.06.2308310; lib: 3.2.4) uniapp微信小程序
  • (Matlab)遗传算法优化的BP神经网络实现回归预测
  • (二)Eureka服务搭建,服务注册,服务发现
  • (附源码)springboot码头作业管理系统 毕业设计 341654
  • (附源码)springboot太原学院贫困生申请管理系统 毕业设计 101517
  • (一)Dubbo快速入门、介绍、使用
  • *Django中的Ajax 纯js的书写样式1
  • .NET CF命令行调试器MDbg入门(一)
  • .net mvc 获取url中controller和action
  • .NET:自动将请求参数绑定到ASPX、ASHX和MVC(菜鸟必看)
  • .netcore如何运行环境安装到Linux服务器
  • .Net转Java自学之路—SpringMVC框架篇六(异常处理)
  • .vollhavhelp-V-XXXXXXXX勒索病毒的最新威胁:如何恢复您的数据?
  • // an array of int
  • /proc/interrupts 和 /proc/stat 查看中断的情况