KVO的使用及底层实现
1、概念
KVO(Key-Value-Observer)也就是观察者模式,是苹果提供的一套事件通知机制。允许对象监听另一个对象特定属性的改变,并在改变时接收到事件,一般继承自NSObject
的对象都默认支持KVO
。
- KVO和NSNotificationCenter都是iOS中观察者模式的一种实现。区别在于:
1、相对于被观察者和观察者之间的关系,KVO是一对一的,而不一对多的。也就是kvo监听到被观察属性值改变时只会通知到观察者,是一对一的关系。而通知模式则是在被观察值改变的时候发送全局通知,任何对象都可以接听到这个通知,是一个一对多的关系;
2、KVO对被监听对象无侵入性,不需要修改其内部代码即可实现监听。而通知需要在被监听对象改变的时候添加发送通知代码。
2、使用
1、
- //1.注册观察者
- /*
- - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- observer:观察者 也就是被观察对象发生改变时通知的接收者
- keyPath:被观察的属性名 比如我们这里是age属性
- options:参数 这里一般选择NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld 也就是在回调方法里会受到被观察属性的旧值和新值,默认为只接收新值。如果想在注册观察者后,立即接收一次回调,则可以加入NSKeyValueObservingOptionInitial枚举。
- context:这个参数可以传入任意类型的对象,这个值会传递到接收消息回调的代码中,是KVO中的一种传值方式。
- */
- [self.per1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
2、
- //2.实现通知回调方法 当被观察对象的属性值发生变化时 就会回调这个方法 change字典中存放KVO属性相关的值,根据options时传入的枚举来返回。
- - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
- NSLog(@"%@---%@----%@---%@",keyPath,object,change,context);
- }
3、
- //3.移除监听
- [self.per1 removeObserver:self forKeyPath:@"age"];
注意点
KVO
的addObserver
和removeObserver
需要是成对的,如果重复remove
则会导致NSRangeException
类型的Crash
,如果忘记remove
则会在观察者释放后再次接收到KVO
回调时Crash
。
苹果官方推荐的方式是,在init
的时候进行addObserver
,在dealloc
时removeObserver
,这样可以保证add
和remove
是成对出现的,是一种比较理想的使用方式。
调用KVO
属性对象时,不仅可以通过点语法和set
语法进行调用,KVO
兼容很多种调用方式:(关于KVC的实现原理接下来会讲到)
- // 1.通过属性的点语法间接调用
- self.per1.age = ;
//2. 直接调用set方法- [self.per1 setAge:123];
// 3.使用KVC的setValue:forKeyPath:方法- [self.per1 setValue:@123 forKeyPath:@"age"];
- //4. 使用KVC的setValue:forKey:方法
- [self.per1 setValue:@123 forKey:@"age"];
- // 5.通过mutableArrayValueForKey:方法获取到代理对象,并使用代理对象进行操作
如果直接修改对象的成员变量是不会触发KVO的:
- //PersonClass.h文件
- #import <Foundation/Foundation.h>
- @interface PersonClass : NSObject{
@public;- NSInteger _age;//成员变量
- }
- //属性
- @property (nonatomic, assign) NSInteger age;
- @end
直接修改成员变量,我们发现没有触发KVO
- self.person1 -> _age = ;
上面全是监听一些基础的数据类型 当被观察属性是一个复杂对象时,比如现在person对象有一个属性animal,那么kvo会如何监听呢?
- #import <Foundation/Foundation.h>
- @class AnimalClass;
- @interface PersonClass : NSObject
- @property (nonatomic, assign) NSInteger age;
- @property (nonatomic, strong) AnimalClass *animal;
- @end
AnimalClass类中有一个name属性
- @interface AnimalClass : NSObject
- @property (nonatomic, copy) NSString *name;
- @end
当我们对animal这个属性进行监听时,发现当对animal的属性值(name)修改时 kvo并不会监听到, 而当给person对象重新赋值一个新的animalClass对象时会被监听到
- //会监听到改变 因为person1的animal属性是个指针 存储的是animal类型的一个地址值 当重新赋值一个alloc出来的新animalClass对象时 animal的地址值发生了改变 会调用person1的setAnimal方法
- AnimalClass *ani2 = [[AnimalClass alloc]init];
- ani2.name = @"cat";
- self.person1.animal = ani2;
- //不会被kvo监听到 因为修改animal的name属性 根本没有调用person1的setAnimal方法 只是调用了animal的setName方法
- self.person1.animal.name = @"cat";
而当我们对person1.animal对象的name属性进行监听时 是可以监听到 self.person1.animal.name = @"cat";这种值改动的
- [self.person1.animal addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
所以kvo能否监听到变化 要看这个被监听对象存储的是什么?实际上是否发生了改变?
3、原理
我们在通过runtime函数object_getclass分别打印person1在添加kvo前后的类对象分别是是PersonClass和NSKVONotifying_PersonClass;
也就是在person1对象注册了kvo以后,其类对象发生了改变
我们在改变age值的时候 实际上是调用了setAge方法 而实例对象调用方法是根绝isa指针找到类对象的对象方法列表找到对应的方法进行调用,所以kvo的本质实际上是重写了被观察属性值的set方法
NSKVONotifying_PersonClass类对象set方法的具体实现:(_NSSet*ValueAndNotify的内部实现)
- didChangeValueForKey:内部会调用observer的observeValueForKeyPath:ofObject:change:context:方法
实现原理
KVO
是通过isa-swizzling
技术实现的(这句话是整个KVO
实现的重点)。在运行时利用RuntimeAPI动态生成一个根据原类创建的中间类(命名规则是NSKVONotifying_xxx
的格式),这个中间类是原类的子类,并动态修改当前对象的isa
指向中间类。
首先重写set方法。在set方法里分别调用willChangeValueForKey->set的赋值操作->didChangeValueForKey 其中didChangeValueForKey在内部视线中会调用观察者的回调方法 返回被观察对象的相关参数
并且将class
方法重写,返回原类的Class(PersonClass类)
。这是因为苹果不想暴露kvo的内部实现,建议在开发中不应该依赖isa
指针,而是通过class
实例方法来获取对象类型。
_isKVOA
方法,这个方法可以当做使用了KVO
的一个标记,系统可能也是这么用的。如果我们想判断当前类是否是KVO
动态生成的类,就可以从方法列表中搜索这个方法。
4、如何手动触发KVO
KVO
在属性发生改变时的调用是自动的,如果在被观察属性值没有改变的情况下手动调用kvo 那么需要时候调用willChangeValueForKey和didChangeValueForKey两个方法(两个方法必须都进行调用 系统在执行didChangeValueForKey方法前会检测willChangeValueForKey是否被调用了)
- [self.person1 willChangeValueForKey:@"age"];
- [self.person1 didChangeValueForKey:@"age"];
手动触发的前提是这个对象已经添加了kvo 如果没有添加的话kvo是无法知道观察者是谁的 也就是不会回调观察者的- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{}这个回调方法的
参考资料
KVO的使用及底层实现的更多相关文章
- KVO的用法、底层实现原理
KVO的用法 KVO也就是key-value-observing(即键值观察),利用一个key来找到某个属性并监听其值得改变.用法如下: 添加观察者 在观察者中实现监听方法,observeValueF ...
- KVC和KVO的理解(底层实现原理)
1.KVC,即是指 NSKeyValueCoding,一个非正式的Protocol,提供一种机制来间接访问对象的属性.而不是通过调用Setter.Getter方法访问.KVO 就是基于 KVC 实现的 ...
- Object-C知识点 (三) 单例 蒙版 刷新 KVO底层
#pragma mark - 单例方法(完整的方法) 系统的单例方法名称 sharedApplication defaultManager standardUserDefaults currentDe ...
- iOS面试必备-iOS基础知识
近期为准备找工作面试,在网络上搜集了这些题,以备面试之用. 插一条广告:本人求职,2016级应届毕业生,有开发经验.可独立开发,低薪求职.QQ:895193543 1.简述OC中内存管理机制. 答:内 ...
- KVO底层实现原理,仿写KVO
这篇文章简单介绍苹果的KVO底层是怎么实现的,自己仿照KVO的底层实现,写一个自己的KVO监听 #pragma mark--KVO底层实现 第一步:新建一个Person类继承NSObject Pers ...
- 【OC底层】KVO原理
KVO的原理是什么?底层是如何实现的? KVO是Key-value observing的缩写. KVO是Objective-C是使用观察者设计模式实现的. Apple使用了isa混写(isa-swiz ...
- IOS-详解KVO底层实现
一.KVO (Key-Value Observing) KVO 是 Objective-C 对观察者模式(Observer Pattern)的实现.也是 Cocoa Binding 的基础.当被观察对 ...
- KVO的底层实现
1.KVO是基于Runtime机制实现的: 2.当某个类的对象的某个属性第一次被观察时,系统会在运行期间动态地创建该类的一个派生类,在这个派生类中重写基类的任何被观察属性的setter方法,派生类在被 ...
- KVO的底层实现原理?如何取消系统默认的KVO并手动触发?
KVO是基于runtime机制实现的 当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类(该类的子类),在这个派生类中重写基类中任何被观察属性的setter 方法.派生类在被 ...
随机推荐
- H5 web存储
H5提供了两种在客户端存储数据的方式:localStorage 持久化的本地存储(浏览器关闭重新打开数据依然存在)sessionStorage 针对一个session的本地存储之前这些都是由cooki ...
- 【hiho一下 第146周】子矩阵求和
[题目链接]:http://hihocoder.com/contest/hiho146/problem/1 [题意] [题解] 设s[i][j]表示左上角的坐标为(i,j)的n*m的矩阵的和; 有s[ ...
- N - Corporate Identity
Beside other services, ACM helps companies to clearly state their “corporate identity”, which includ ...
- RPC框架分析
RPC框架分析 常用的框架 .net(WCF) .net中分布式框架集大成者,提供多种通信方式,多种安全策略的调用(配置繁琐). java 1.RMI JDK原生(严格的说来算不上框架). 2.Du ...
- N天学习一个linux命令之netstat
用途 打印网络连接,路由表,网卡信息,假连接,组播成员信息 用法 1 显示网络连接信息 netstat [address_family_options] [--tcp|-t] [--udp|-u] [ ...
- JS 仿淘宝幻灯片 非完整版 小案例
仿淘宝幻灯片,基础版,后期效果是要做到每次点击小圆点,切换都无缝 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" &quo ...
- webservices系列(五)——javaweb整合Axis2及多service配置
1.新建一个项目动态web项目webservice_test3. 2.打开<Tomcat安装目录>webapps/axis2/WEB-INF.将lib.conf.modules三个目录复制 ...
- Navicat 提示Cannot create oci environment 解决方式
一直在使用Navicat,这是一个数据库client软件.能连接多种不同类型的数据库,给我们的日常的工作带来了不少的便捷.近期.我在电脑上安装了orcale,然后,Navicat就莫名其妙的不能连接o ...
- 一键免费升级Windows 10
2015年3月18日,在深圳召开的微软Windows硬件project产业创新峰会(WinHEC)发布了一些震撼消息. 微软计划于今年夏天正式推出Windows 10操作系统.将在190个国家发布,总 ...
- [面试题]java中final finally finalized 的差别是什么?
final 是修饰符,能够用于修饰变量.方法和类.修饰变量时.代表变量不能够改动,也就是常量了.常量须要在定义时赋值或通过构造函数赋值,两者仅仅能选其一:修饰方法时,代表方法仅仅能调用,不能被 ove ...