14.Notification与KVO

Notification

1.创建一个通知对象:使用notificationWithName:object:或者notificationWithName:object:userInfo:

 NSNotification* notification = [NSNotification notificationWithName:kImageNotificationLoadFailed(connection.imageURL)
                                                                 object:self
                                                               userInfo:[NSDictionary dictionaryWithObjectsAndKeys:error,@"error",connection.imageURL,@"imageURL",nil]];

这里需要注意的是,创建自己的通知并不是必须的。而是在创建自己的通知之前,采用NSNotificationCenter类的方法postNotificationName:object:userInfo:更加便利的发出通知。
这种情况,一般使用NSNotificationCenter的类方法defaultCerter就获得默认的通知对象,这样你就可以给改程序的默认通知中心发送通知了。
注意:每一个程序都有一个自己的通知中心,即NSNotificationCenter对象。该对象采用单例设计模式,采用defaultCenter方法就可以获得唯一的NSNotificationCenter对象。
注意:NSNotification对象是不可变的,因为一旦创建,对象是不能更改的。
2.注册通知:addObserverLselector:name:object:

可以看到除了添加观察者之外,还有其接收到通知之后的执行方法入口,即selector的实参。因此为了进行防御式编程,最好先检查观察者是否定义了该方法。例如:添加观察者代码有

[[NSNotificationCenter defaultCenter] addObserver:self
    selector:@selector(aWindowBecameMain:)
    name:NSWindowDidBecomeMainNotification object:nil];

这里保证了self定义了aWindowBecameMain:方法。而对于一个任意的观察者observer,不能保证其对应的selectoraWindowBecameMain:,可采用[observer respondsToSelector:@selector(aWindowBecameMain:)]进行检查。所以完整的添加观察者过程为:

if([observer respondsToSelector:@selector(aWindowBecameMain:)]) {
        [[NSNotificationCenter defaultCenter] addObserver:observer selector:@selector(aWindowBecameMain:) name:NSWindowDidBecomeMainNotification object:nil];
    }

注意到addObserver:selector:name:object:不仅指定一个观察者,指定通知中心发送给观察者的消息,还有接收通知的名字,以及指定的对象。一般来说不需要指定nameobject,但如果仅仅指定了一个object,观察者将收到该对象的所有通知。例如将上面的代码中name改为nil,那么观察者将接收到object对象的所有消息,但是确定不了接收这些消息的顺序。
如果指定一个通知名称,观察者将收到它每次发出的通知。例如,上面的代码中objectnil,那么客户对象(self)将收到任何对象发出NSWindowDidBecameMainNotification通知。如果既没有指定object,也没有指定name,那么该观察者将收到所有对象的所有消息。
3.发送通知:POSTNotificationName:object:或者performSelectorOnMainThread:withObject:waitUntilDone:

例如程序可以实现将一个文本可以进行一系列的转换,例如对于一个实例、RTF格式转换成ASCLL格式。而转换在一个类(如Converter类)的对象中得到处理,在程序执行过程中可以加入或者删除这种转换。而且当添加或者删除Converter操作时,你的程序可能需要通知其他的对象,但是这些Converter对象并不需要知道被通知对象是什么,能干什么。
你只需要声明两个通知,“ConverterAdded”和“ConverterRemoved”,并且在某一事件发生时就发出这个两个通知。
当一个用户安装或者删除一个Converter,它将发送下面的消息给通知中心:

[[NSNotificationCenter defaultCenter]
    postNotificationName:@"ConverterAdded" object:self];

或者是

[[NSNotificationCenter defaultCenter]
    postNotificationName:@"ConverterRemoved" object:self];

通知中心将会区分它们对象对这些通知感兴趣并且通知他们。如果出了关心观察者的通知名称和观察的对象,还关心其他之外的对象,那么就把之外的对象放在通知的可选字典中,或者用方法POSTNotificationName:object:userInfo:
而采用performSelectorOnMainThread:withObject:waitUntilDone:则是直接调用NSNotification的方法postNotification,而postNotificationNameobject参数可以放到withObject的实参中。例如:

[[NSNotificationCenter defaultCenter] performSelectorOnMainThread:@selector(postNotification:) withObject:notification waitUntilDone:YES];//注意这里的notification为自定义的一个通知对象,可定义为
NSNotification* notification = [NSNotification notificationWithName:@"ConverterAdded"object:self];//那么它的作用与上面的一致

4.移除通知:removeObserver:和removeObserver:name:object:

其中,removeObserver:是删除通知中心保存的调度表一个观察者的所有入口,而removeObserver:name:object:是删除匹配了通知中心保存的调度表中观察者的一个入口。

这个比较简单,直接调用该方法就行。例如:

[[NSNotificationCenter defaultCenter] removeObserver:observer name:nil object:self];

注意参数notificationObserver为要删除的观察者,一定不能置为nil。

PS:这里简单说一下通知中心保存的调度表。通知中心的调度表是给一些观察者指定的一些通知集。一个通知集是通知中心发出的通知的子集。每个表的入口包含:

通知观察者(必须要的)、通知名称、通知的发送者。
下图表示通知集中指定的通知的调用表入口的四种类型:

14.Notification与KVO_第1张图片
屏幕快照 2017-06-21 下午6.09.05.png

下图表示四种观察者的调度表

14.Notification与KVO_第2张图片

最后,提醒一下观察者收到通知的顺序是没有定义的。同时通知发出和观察的对象有可能是一样的。通知中心同步转发通知给观察者,就是说 postNotification: 方法直到接收并处理完通知才返回值。要想异步的发送通知,可以使用NSNotificationQueue。在多线程编程中,通知一般是在一个发出通知的那个线程中转发,但也可能是不在同一个线程中转发通知。

KVO概念:

KVOcocoa中的一个核心概念,简单理解就是:关注Model某个数据(Key)的对象可以注册为监听器,一旦Model某个KeyValue发生变化,就会广播给所有的监听器

KVO机制:

KVO(Key Value Observe)cocoa中用来设值或取值的协议(NSKeyValueCoding),跟Java的ejb有点类似,通过对变量和函数名进行规范达到方便设置类成员值的目的。它有点类似于Notification,但是,它提供了观察某一属性变化的方法,而Notification需要一个发送notification的对象,这样KVO就比Notification极大的简化了代码。这种观察-被观察模型适用于这样的情况,比方说根据A(数据类)的某个属性值变化,B(view类)中的某个属性做出相应变化。对于推崇MVCcocoa而言,kvo应用价值很高。

KVO实现原理

  1. KVO是基于runtime机制实现的当某个类的属性对象第一次被观察时,系统就会在运行期动态的创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter方法。派生类在被重写的setter方法内实现真正的通知机制
  1. 如果原类为Persoon,那么生成的派生类名为NSKVONotifying_Person每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法
  1. 键值观察通知依赖于NSObject的两个方法:willChangeValueForKey:和didChangeValueForKey:;在一个被观察属性发生改变之前,willChangeValueForKey:一定会被调用,这就会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而observerValueForKey:ofObject:change:context:也会被调用。

补充:KVO的这套实现机制中苹果还偷偷重写了class方法,让我们误认为还是使用的当前类,从而达到隐藏生成的派生类

要通过遵循 KVO 的属性设置方式来变更属性值,如果仅是直接修改属性对应的成员变量,是无法实现 KVO的

适用kvo时,通常遵循如下流程:

1 注册:

-(void)addObserver:(NSObject )anObserver forKeyPath:(NSString )keyPath options:(NSKeyValueObservingOptions)options context:(void *)context

keyPath就是要观察的属性值,options给你观察键值变化的选择,而context方便传输你需要的数据(注意这是一个void型)

2 实现变化方法:

-(void) observeValueForKeyPath:(NSString )keyPath ofObject:(id)objectchange:(NSDictionary )change context:(void *)context

change里存储了一些变化的数据,比如变化前的数据,变化后的数据;如果注册时context不为空,这里context就能接收到。

是不是很简单?kvo的逻辑非常清晰,实现步骤简单。
说了这么多,大家都要跃跃欲试了吧。
可是,在此之前,我们还需要了解KVC机制。其实,知道了kvo的逻辑只是帮助你理解而已,要真正掌握的,不在于kvo的实现步骤是什么,而在于KVC,因为只有符合KVC标准的对象才能使用kvo(强烈推荐要使用kvo的人先理解KVC)。

KVC是一种间接访问对象属性(用字符串表征)的机制,而不是直接调用对象的accessor方法或是直接访问成员对象。key就是确定对象某个值的字符串,它通常和accessor方法或是变量同名,并且必须以小写字母开头。

在 Swift 中使用 KVO 的前提条件:
1.观察者和被观察者都必须是 NSObject 的子类;
2.观察的属性需要使用 @dynamic 关键字修饰。这与在 OC 中几乎没有多少门槛相比,确实麻烦了许多。

例子

dynamic var label : UILabel!
        
label.addObserver(self, forKeyPath: "text", options: NSKeyValueObservingOptions.new, context: nil)
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        
        print("触发了KVO啦啦啦啦")
}

KVO与Notification之间的区别:

notification是需要一个发送notification的对象,一般是notificationCenter,来通知观察者。
KVO是直接通知到观察对象,并且逻辑非常清晰,实现步骤简单。

你可能感兴趣的:(14.Notification与KVO)