说明:为了区别「本地通知」与「推送通知」这两种iOS中提醒用户,可见的「通知」,本文所将Notification翻译为「通告」。它们的详细区别,可参考《iOS开发系列--通知与消息机制》一文。
实践遇到的问题:
最近在维护公司的一个项目中,遇到这样一个报错:-[GlobalManager addAlbum:]: unrecognized selector sent to instance
经排查,原因如下:以前同事在利用「通告机制」在GlobalManager类中把「自己/self」注册为「观察器」(用了addObserver: selector: name: object:方法),但是没有在注册观察器的类(GlobalManager类)中实现selector参数中的方法(他在其他类实现了)。所以就报上述错误(其实报错中字面也说得挺明白的,只是总意会不到英文中的那个意思)。
解决:将 selector参数的方法写在当前类中。或者删除注册「观察器」的代码,即不报这个错。
再经过后来的查阅资料,得出结论:如果用addObserver: selector: name: object:方法向「通告中心」注册「观察器」,第二个参数,即selector:中的方法,必须在当前类中实现,如果写在其他类,就会报上述错误。详见图一:
扩展:另一个注册观察器的方法
另外,注册观察器还可以用另外一个方法:
addObserverForName: object: queue: usingBlock:
这个是利用block的形式进行「回调」,代码更简洁、直观。而上面的方法是「利用@selector关键字传递SEL类型的函数名」进行「回调」。
图一:注册「观察器」
插播:「回调/callback」
上述「通告机制」,涉及到「回调」这个概念,因为维基百科上面的定义有点抽象,我自己理解就是:某段代码/函数,需要由特定用户事件来触发,就是「回调」,没有这个事件,就不会执行这段代码。术语就是「通过『函数指针』调用的函数」。
而根据《Objective-C Programming 2nd Edition》这本书,iOS的回调,分为四类:
1.目标-动作对/Target-action;
2.辅助对象/Helper objects;(包括「委托/delegates」、「数据源/data sources」)
3.通告/Notifications;
4.Block。
这里只简单复述iOS回调的几种类型,至于何种情况用何种回调,可以看书中介绍。(我自己也需要更多实践去体验。)
详解:「通告机制」
可以看到,第三种回调:「通告」,就是我们上面应用的「通告机制」。它是基于「观察者模式」的。
「通告机制」在代码层面,涉及两个类:NSNotification类及NSNotificationCenter类,它们都定义在“NSNotification.h”文件中。
NSNotification类:
代表「通告」的内容载体有三个属性:name(通告的名称),object(通告的发送者/谁发送这个通告),userInfo(通告的附加信息/参数)
此外,NSNotification类及它的扩展类(category)还有5个初始化/实例化「通告」的方法,详情可在Xcode中查看。
NSNotificationCenter类:
是通告系统的中心,用于获取通告中心、注册、移除观察器、发送通告。有8个方法。详情可在Xcode中查看。
因此,我们可以总结「应用「通告机制」的步骤」:
1.注册观察器。
这一步解决谁观察通告中心,观察通什么通告,观察通谁的通告,接到通告后执行什么方法这些问题。
注册观察器的方法
addObserver: selector: name: object:
addObserverForName: object: queue: usingBlock:
2.实现selector中的方法或block中的代码
这一步具体实现接到通告后执行什么动作。
3.向通告中心发布/post通告。触发回调, 实现最终要的效果。(此步骤可选)
注意,这里由谁发送通告(在哪个类中写发送通告代码),要看触发事件是在哪个控制器类中。
另外,如果观察一些系统通告,如UIDevice的这四种「通告」(UIDeviceOrientationDidChangeNotification、UIDeviceBatteryStateDidChangeNotification、UIDeviceBatteryLevelDidChangeNotification、UIDeviceProximityStateDidChangeNotification),则由系统自动发布通告,无需自己实现。
发布通告的方法:
postNotification:
postNotificationName: object:
postNotificationName: object: userInfo:
4.将注册为观察器的对象移出通告中心
可用方法:
removeObserver:
removeObserver: name: object:
范例:
在工程中AppDelegate类中,添加以下代码,可观察/监测是否有物体接近屏幕。
1 UIDevice *device = [UIDevice currentDevice]; 2 // 打开近身监视功能 3 [device setProximityMonitoringEnabled:YES]; 4 5 // 注册观察器 6 [[NSNotificationCenter defaultCenter] addObserverForName:UIDeviceProximityStateDidChangeNotification 7 object:nil 8 queue:[NSOperationQueue mainQueue] 9 usingBlock:^(NSNotification *note) { 10 NSLog(@"有物体接近屏幕了"); 11 }];
近身监测功能的实现
扩展:「架构模式/Architectural pattern」与「设计模式/design pattern」
到此,对「通告机制」的基本原理,具体实现有了一定了解。不过在研究「通告机制」的时候,接触到「观察者模式」,继而又接触到「设计模式」及「架构模式」,一下子信息量太大,感觉如坠云中,只见树木,不见森林。于是又研究了一阵(主要参考维基百科的资料),总结并画出下图二,感觉能大概从宏观上把握这些概念了。(不知有无谬误,如有发现,还请斧正,谢谢。)
图二:对「架构模式」及「设计模式」的理解
而关于「MVC模式」及「观察者模式」在整个「软件设计模式/Software design patterns」中的位置,则如下图三:
图三:「MVC」及「观察者模式/Observer」在整个「软件设计模式/Software design patterns」中的位置
突然间,感觉舒服蛮多了。