设计模式-观察者模式KVO

1.什么是观察者模式?
2.为什么要用观察者模式?它的优缺点是什么?
![Uploading 屏幕快照 2016-12-20 下午3.23.56_660666.png . . .]
3.观察者模式解决了什么问题?你的项目中哪里用到了观察者模式?
4.观察者模式怎么用?有几种方式?
5.怎么创建观察者模式?
7.写观察者模式的时候需要注意什么问题?
8.iOS源代码中哪里用到了观察者模式?举例说明。
9.实现原理,

根据上面的套路,我们一一回答问题。
1.什么是观察者模式

KVO 是 Objective-C 对观察者设计模式的一种实现。【另外一种是:通知机制(notification)
KVO提供一种机制,指定一个被观察对象(例如A类),当对象某个属性(例如A中的字符串name)发生更改时,对象会获得通知,并作出相应处理;【且不需要给被观察的对象添加任何额外代码,就能使用KVO机制】
在MVC设计架构下的项目,KVO机制很适合实现mode模型和view视图之间的通讯。
例如:代码中,在模型类A创建属性数据,在控制器中创建观察者,一旦属性数据发生改变就收到观察者收到通知,通过KVO再在控制器使用回调方法处理实现视图B的更新;(本文中的应用就是这样的例子.)

简单的说就是,当某对象改变时,自动通知所有相关的状态进行更新。

2.为什么要用观察者模式
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

观察者模式的优点:
1、 Subject和Observer之间是松偶合的,分别可以各自独立改变。
2、Subject在发送广播通知的时候,无须指定具体的Observer,Observer可以自己决定是否要订阅Subject的通知。
3、高内聚、低偶合。

3.观察者模式解决了什么问题?你的项目中哪里用到了观察者模式?
有时候我们需要监听某个类的属性值的变化从而做出相应的改变,这个时候使用KVO/KVC设计模式。
比如,在项目中,我需要监听model中的某个属性值的变换,当变化时,需要更新UI显示。

//增加frame的监听 
1)用于判断当前手势滑动的frame moveTableView:didChangeFromFrame:toFrame
2)增加搜索结果contentview 根据手势切换导航显示模式
    [self addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];

4.观察者模式怎么用?有几种方式?
在iOS中观察者模式的实现有四种方法:NSNotification、KVO、Protocol以及Code Block代码块。

要点:
Notification是一对多的,而delegate回调是一对一的。

Notification - NotificationCenter机制使用了操作系统的对象间通讯功能,而delegate是直接的函数调用。Notification跨度大,而delegate效率可能比较高。

相较于前两者KVO才是一种真正的观察者模式,它允许你将一个处理函数绑定到某个类的属性,属性发生改变是就会自动触发,不像其他两种需要你手动的发通知。KVO是一种非常灵活的观察机制,广泛应用于界面设计。

Code Block其实就相当于C的函数指针,可以用来做各种回调。我觉得其应当具备最高的效率。使用Code Block要注意的地方就是使用外部变量。在block里直接引用外部变量的话会在block定义的时候复制外部变量的一个拷贝,也就是说得到的是block定义时的值,在block内修改这个值也不会传给外部。要得到实时的数据,或者将数据传出的话需要在相关变量前面加__block即可。

NSNotification

屏幕快照 2016-12-20 下午3.22.55.png

发送通知:

屏幕快照 2016-12-20 下午3.25.41.png

实现通知的方法


屏幕快照 2016-12-20 下午3.23.56.png

KVO

//注册通知
 [_tableView addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
//通知回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"frame"]) {
        //处理逻辑
    }
}
//移除通知
- (void)dealloc
{
    [_tableView removeObserver:self forKeyPath:@"frame"];
}

protocol/delegate

需要注意的问题:
1)A对象要通知B对象,B对象必须实现监听的方法,否则一旦有消息发送就会导致崩溃.

  1. A对象不想通知B对象了,需要从B对象身上移除掉通知.

但是要注意:添加的观察者的次数要和移除观察者的次数相等,少移除一个或者多移除一个都会造成程序崩溃:
3)严重依赖于string
KVO严重依赖string,换句话说,KVO中的keyPath必须是NSString这个事实使得编译器没办法在编译阶段将错误的keyPath给找出来;譬如很容易将「contentSize」写成「content size」;
4)

KVO的实现
KVO的实现也依赖于Objective-C的Runtime。
简单概述下KVO的实现:
当你观察一个对象(称该对象为「被观察对象」)时,一个新的类会动态被创建。这个类继承自「被观察对象」所对应类的,并重写该被观察属性的setter方法;针对setter方法的重写无非是在赋值语句前后加上相应的通知;最后,把「被观察对象」的isa指针(isa指针告诉Runtime系统这个对象的类是什么)指向这个新创建的中间类,对象就神奇变成了新创建类的实例。
根据文档的描述,虽然被观察对象的isa指针被修改了,但是调用其class方法得到的类信息仍然是它之前所继承类的类信息,而不是这个新创建类的类信息。
补充:下面对isa指针和类方法class作以更多的说明。
isa指针和类方法class的返回值都是Class类型,如下:
@interfaceNSObject {
ClassisaOBJC_ISA_AVAILABILITY;
}

  • (Class)class;
    根据我的理解,一般情况下,isa指针和class方法返回值都是一样的;但KVO底层实现时,动态创建的类只是重写了被观察属性的setter方法,并未重写类方法class,因此向被观察者发送class消息实际上仍然调用的是被观察者原先类的类方法+ (Class)class,得到的类型信息当然是原先类的类信息,根据我的猜测,isKindOfClass:和isMemberOfClass:与class方法紧密相关。
    国外的大神Mike Ash早在2009年就做了关于KVO的实现细节的探究,更多详细参考这里

下面来对这两个参数进行详细介绍。
options
options可选值是一个NSKeyValueObservingOptions枚举值,到目前为止,一共包括四个值,在介绍这四个值各自表示的意思之前,先得有一个概念,即KVO响应方法有一个NSDictionary类型参数change(下面『响应』中可以看到),这个字典中会有一个与被监听属性相关的值,譬如被改变之前的值、新值等,NSDictionary中有啥值由『订阅』时的options值决定,options可取值如下:
NSKeyValueObservingOptionNew: 指示change字典中包含新属性值;
NSKeyValueObservingOptionOld: 指示change字典中包含旧属性值;
NSKeyValueObservingOptionInitial: 相对复杂一些,NSKeyValueObserving.h文件中有详细说明,此处略过;
NSKeyValueObservingOptionPrior: 相对复杂一些,NSKeyValueObserving.h文件中有详细说明,此处略过;
现在细想,options这个参数也忒复杂了,难怪大神们觉得这个API丑陋(不过我等小民之前从未想过这个问题,=_=,没办法,Apple是个大帝国,我只是其中一个跪舔的小屁民)。
不过更糟心的是下面的context参数。

context
options信息量稍大,但其实蛮好理解的,然而对于context,在写这篇博客之前,一直不知道context参数有啥用(也没在意)。
context作用大了去了,在上文『KVO的槽点』提到一个槽点『多次相同的removeObserver会导致crash』。导致『多次调用相同的removeObserver』一个很重要的原因是我们经常在addObserver时为context参数赋值NULL,关于如何使用context参数,下面的『响应』中会提到。

响应
iOS的UI交互(譬如UIButton的一次点击)有一个非常不错的消息转发机制 — Target-Action模型,简单来说,为指定的event指定target和action处理方法。
UIButtonbutton = [UIButtonnew];
[button addTarget:selfaction:@selector(buttonDidClicked:) forControlEvents:UIControlEventTouchUpInside];
这种target-action模型逻辑非常清晰。作为对比,KVO的响应处理就非常糟糕了,所有的响应都对应是同一个方法- (void)observeValueForKeyPath:ofObject:change:context:,其原型如下:
-(void)observeValueForKeyPath:(NSString
)keyPath
ofObject:(id)object
change:(NSDictionary*)change
context:(void *)context;
除了NSDictionary类型参数change之外,其余几个参数都能在–addObserver:forKeyPath:options:context:找到对应。

下面将针对「严重依赖于string」和「多次相同的removeObserver会导致crash」这两个槽点对keyPath和context参数进行阐述。
keyPath
keyPath的类型是NSString,这导致了我们使用了错误的keyPath而不自知,譬如将@”contentSize”错误写成@”contentsize”,一个更好的方法是不直接使用@”xxxoo”,而是积极使用NSStringFromSelector(SEL aSelector)方法,即改@"contentSize"为NSStringFromSelector(@selector(contentSize))。

context
对于context,上文已经提到一种场景:假如父类(设为ClassA)和子类(设为ClassB)都监听了同一个对象肿么办?是ClassB处理呢还是交给父类ClassA的observeValueForKeyPath:ofObject:change:context:处理呢?更复杂一点,如果子类的子类(设为ClassC)也监听了同一个对象,当ClassB接收到ClassC的[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];消息时又该如何处理呢?

用context参数判断!

在addObserver时为context参数设置一个独一无二的值即可,在responding处理时对这个context值进行检验。如此就解决了问题,但这需要靠用户(各个层级类的程序员用户)自觉遵守。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容