Men的博客

欢迎光临!

0%

KVO

KVO实现原理

apple使用isa混写(isa-swizzling)来实现KVO。
当观察对象A时,KVO机制动态创建一个新的名为:NSKVONotifying 
A的新类,该类继承自对象A的本类,
且KVO为NSKVONotifying A重写观察属性的setter方法,
setter方法会负责在调用原setter方法之前之后,
通知所有观察对象属性值的更改情况。
对象注册为观察者时,isa指针指向新子类,那么这个被观察的对象就神奇地
变成新子类的对象了。) 因而在该对象上对 setter 的调用就会调用已重
写的 setter,从而激活键值通知机制。
子类setter方法剖析:KVO的键值观察通知依赖于 NSObject 的两个方法
:willChangeValueForKey:和 didChangevlueForKey:,
在存取数值的前后分别调用2个方法:被观察属性发生改变之前,
willChangeValueForKey:被调用,通知系统该 keyPath 的属性值即将
变更;当改变发生后, didChangeValueForKey: 被调用,通知系统该
keyPath 的属性值已经变更;之
后observeValueForKey:ofObject:change:context:
也会被调用。且重写观察属性的setter 方法这种继承方式的注入是
在运行时而不是编译时实现的。
观察者观察的属性,只有遵循KVO变更属性值的方式才会执行KVO
的回调方法,例如是否执行setter方法,或着是否使用KVC
赋值。如果赋值没有通过setter方法或者KVC,而是直接修改属
性对应的成员变量,例如:仅调用_name = @"newName" ,这
时时不会触发kvo机制,更加不会调用回调方法的。所以使用
KVO机制的前提是遵循KVO的属性设置方式来变更属性值。

KVO流程

1.注册A类的name属性变化监听
    [a addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
2.创建A的子类NSKVONotifyingA
    Class noA = objc_allocateClassPair([A class], "NSKVONotifyingA", observer);
3.isa指针混合
    将A类的指针指向NSKVONotifyingA,从而到达调用NSKVONotifyingA类的方法的目的
4.当调用a的setName方法时进入到NSKVONotifyingA类的setName方法
5.调用willChangeValueForKey回调属性将要变化
6.调用NSKVONotifyingA类的【self setName】赋值,实际上执行的是A类的setName,达到赋值目的
7.调用didChangeValueForKey回调属性变化了

为什么要用子类监听setter方法,而不用分类呢?

原因是当你用分类监听setter方法的时候,原类中setter方法就不会走了,这样不好,所以苹果使用了子类监听setter方法。

isa-swizzling为什么不会出现递归

你在swizzlingViewDidLoad方法中又调用[self
swizzlingViewDidLoad];,这难道不会产生递归调用吗?
其实不会,Method Swizzling :实现原理理解为方法互换,
假设我们将A,B两个方法进行互换,向A发消息时执行的是B的方
法,向B发消息时执行的是A方法,所以再调用[self
swizzlingViewDidLoad];又回到原类的方法中了。

为什么要保证只执行一次Method Swizzling

我们上面提到过Method Swizzling的实现原理就是对类的Dispatch
Table进行操作,每进行一次Swizzling就交换一次SEL和IMP(可以理解为
函数指针),如果Swizzling被执行了多次,就相当于SEL和IMP被交换了多
次。这就会导致第一次执行成功交换了、第二次执行又换回去了、第三次执
行.....这样换来换去的结果,能不能成功就看运气了😄,这也是好多人说
Method Swizzling不好用的原因之一。
为了避免这样的原因出现,我们可以通过GCD的dispatch_once函数来解决,
利用dispatch_once函数内代码只会执行一次的特性。

Method Swizzling为什么对类簇不起作用

对于NSArray NSMutableArray NSDictionary
NSMutableDictionary 等类进行Method Swizzling ,实现
方式按照上面例子来做,但是你发现Method Swizzling 根本不
起作用
这是因为Method Swizzling 对NSArray这些类簇是不起作
用的,因为这些类簇,其实是一种抽象工厂的设计模式,抽象工厂内
部有很多其他继承自身当前类的子类,抽象工厂类会根据不同情况,
创建不同的抽象对象来进行使用。例如我们调用NSArray的
objectAtIndex方法,这个类会在方法内部判断,内部创建不同抽
象类进行操作。
所以也就是我们对NSArray进行操作其实是对父类进行操作,在
NSArray内部会创建其子类进行执行操作,真正执行操作的并不是
NSArray自身,所以我们应该对其真身进行操作。
如果进行Method Swizzling中的类有继承关系,并且
swizzling了同一个方法,这样会导致父类的Swizzling失效。必
须要证Method Swizzling方法只执行一次。

KVC与KVO的不同

KVC(键值编码),即Key-Value Coding,一个非正式的Protocol,
使用字符串(键)访问一个对象实例变量的机制。而不是通过调用Setter
、Getter方法等显式的存取方式去问。
KVO(键值监听),即Key-Value Observing,它提供一种机制,
当指定的对象的属性被修改后,对象就会接受到通知,前提是执行了
setter方法、或者使用了KVC赋值。

KVO和notification(通知)的区别

notification比KVO多了发送通知的一步。两者都是一对多,
但是对象之间直接的交互,notification明显得多,需要
notificationCenter来做为中间交互。而KVO如我们介绍的,设置观察者->
处理属性变化,至于中间通知这一环,则隐秘多了,只留一句“交由系统通知”,
具体的可参照以上实现过程的剖析。
notification的优点是监听不局限于属性的变化,还可以对多种多样的状态
变化进行监听,监听范围广,例如键盘、前后台等系统通知的使用也更显灵活方
便。

KVO和delegate(通知)的区别

和delegate一样,KVO和NSNotification的作用都是类与类之间的通信。
但是与delegate不同的是:
这两个都是负责发送接收通知,剩下的事情由系统处理,所以不用返回值;而
delegate 则需要通信的对象通过变量(代理)联系;
delegate一般是一对一,而这两个可以一对多。

KVO和线程关系

总结:接收通知代码 由 发出通知线程决定, KVO也一样

当你创建的一个类的名字和kvo动态生成的子类的名字冲突了 会发生什么

亲自尝试一下,会在日志台打下如下日志
KVO failed to allocate class pair for name 
NSKVONotifying_myKVO, automatic key-value 
observing will not work for this class
KVO未能分配类两名NSKVONotifying_myKVO,自动键-值观察不
会为这个类工作