一个NSNotificationCenter对象(通知中心)提供了在程序中广播消息的机制,它实质上就是一个通知分发表。这个分发表负责维护为各个通知注册的观察者,并在通知到达时,去查找相应的观察者,将通知转发给他们进行处理。html
本文主要了整理了一下NSNotificationCenter的使用及须要注意的一些问题,并提出了一些未解决的问题,但愿能在此获得解答。安全
每一个程序都会有一个默认的通知中心。为此,NSNotificationCenter提供了一个类方法来获取这个通知中心:多线程
+ (NSNotificationCenter *)defaultCenter
获取了这个默认的通知中心对象后,咱们就可使用它来处理通知相关的操做了,包括注册观察者,移除观察者、发送通知等。并发
一般若是不是出于必要,咱们通常都使用这个默认的通知中心,而不本身建立维护一个通知中心。app
若是想让对象监听某个通知,则须要在通知中心中将这个对象注册为通知的观察者。早先,NSNotificationCenter提供了如下方法来添加观察者:框架
- (void)addObserver:(id)notificationObserver selector:(SEL)notificationSelector name:(NSString *)notificationName object:(id)notificationSender
这个方法带有4个参数,分别指定了通知的观察者、处理通知的回调、通知名及通知的发送对象。这里须要注意几个问题:async
对于以上几点,咱们来重点关注一下第3条。如下代码演示了当咱们的notificationName设置为nil时,通知的监听状况。post
代码清单1:添加一个Observer,其中notificationName为nil性能
@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:nil object:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil]; } - (void)handleNotification:(NSNotification *)notification { NSLog(@"notification = %@", notification.name); } @end
运行后的输出结果以下:测试
notification = TestNotification notification = UIWindowDidBecomeVisibleNotification notification = UIWindowDidBecomeKeyNotification notification = UIApplicationDidFinishLaunchingNotification notification = _UIWindowContentWillRotateNotification notification = _UIApplicationWillAddDeactivationReasonNotification notification = _UIApplicationDidRemoveDeactivationReasonNotification notification = UIDeviceOrientationDidChangeNotification notification = _UIApplicationDidRemoveDeactivationReasonNotification notification = UIApplicationDidBecomeActiveNotification
能够看出,咱们的对象基本上监听了测试程序启动后的所示消息。固然,咱们不多会去这么作。
而对于第4条,使用得比较多的场景是监听UITextField的修改事件,一般咱们在一个ViewController中,只但愿去监听当前视图中的UITextField修改事件,而不但愿监听全部UITextField的修改事件,这时咱们就能够将当前页面的UITextField对象指定为notificationSender。
在iOS 4.0以后,NSNotificationCenter为了跟上时代,又提供了一个以block方式实现的添加观察者的方法,以下所示:
- (id<NSObject>)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block
你们第一次看到这个方法时是否会有这样的疑问:观察者呢?参数中并无指定具体的观察者,那谁是观察者呢?实际上,与前一个方法不一样的是,前者使用一个现存的对象做为观察者,而这个方法会建立一个匿名的对象做为观察者(即方法返回的id<NSObject>对象),这个匿名对象会在指定的队列(queue)上去执行咱们的block。
这个方法的优势在于添加观察者的操做与回调处理操做的代码更加紧凑,不须要拼命滚动鼠标就能直接找处处理代码,简单直观。这个方法也有几个地方须要注意:
下面咱们重点说明一下第2点和第3点。
关于第2点,当咱们指定一个Operation Queue时,无论通知是在哪一个线程中post的,都会在Operation Queue所属的线程中进行转发,如代码清单2所示:
代码清单2:在指定队列中接收通知
@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserverForName:TEST_NOTIFICATION object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { NSLog(@"receive thread = %@", [NSThread currentThread]); }]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"post thread = %@", [NSThread currentThread]); [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil]; }); } @end
在这里,咱们在主线程里添加了一个观察者,并指定在主线程队列中去接收处理这个通知。而后咱们在一个全局队列中post了一个通知。咱们来看下输出结果:
post thread = <NSThread: 0x7ffe0351f5f0>{number = 2, name = (null)} receive thread = <NSThread: 0x7ffe03508b30>{number = 1, name = main}
能够看到,消息的post与接收处理并非在同一个线程中。如上面所提到的,若是queue为nil,则消息是默认在post线程中同步处理,你们能够试一下。
对于第3点,因为使用的是block,因此须要注意的就是避免引发循环引用的问题,如代码清单3所示:
代码清单3:block引起的循环引用问题
@interface Observer : NSObject @property (nonatomic, assign) NSInteger i; @property (nonatomic, weak) id<NSObject> observer; @end @implementation Observer - (instancetype)init { self = [super init]; if (self) { NSLog(@"Init Observer"); // 添加观察者 _observer = [[NSNotificationCenter defaultCenter] addObserverForName:TEST_NOTIFICATION object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { NSLog(@"handle notification"); // 使用self self.i = 10; }]; } return self; } @end #pragma mark - ViewController @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self createObserver]; // 发送消息 [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil]; } - (void)createObserver { Observer *observer = [[Observer alloc] init]; } @end
运行后的输出以下:
Init Observer handle notification
咱们能够看到createObserver中建立的observer并无被释放。因此,使用 – addObserverForName:object:queue:usingBlock:必定要注意这个问题。
与注册观察者相对应的,NSNotificationCenter为咱们提供了两个移除观察者的方法。它们的定义以下:
- (void)removeObserver:(id)notificationObserver - (void)removeObserver:(id)notificationObserver name:(NSString *)notificationName object:(id)notificationSender
前一个方法会将notificationObserver从通知中心中移除,这样notificationObserver就没法再监放任何消息。然后一个会根据三个参数来移除相应的观察者。
这两个方法也有几点须要注意:
关于注册监听者,还有一个须要注意的问题是,每次调用addObserver时,都会在通知中心从新注册一次,即便是同一对象监听同一个消息,而不是去覆盖原来的监听。这样,当通知中心转发某一消息时,若是同一对象屡次注册了这个通知的观察者,则会收到多个通知,如代码清单4所示:
代码清单4:同一对象屡次注册同一消息
@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:TEST_NOTIFICATION object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:TEST_NOTIFICATION object:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil]; } - (void)handleNotification:(NSNotification *)notification { NSLog(@"notification = %@", notification.name); } @end
其输出结果以下所示:
notification = TestNotification notification = TestNotification
能够看到对象处理了两次通知。因此,若是咱们须要在viewWillAppear监听一个通知时,必定要记得在对应的viewWillDisappear里面将观察者移除,不然就可能会出现上面的状况。
最后,再特别重点强调的很是重要的一点是,在释放对象前,必定要记住若是它监听了通知,必定要将它从通知中心移除。若是是用 – addObserverForName:object:queue:usingBlock:,也记得必定得移除这个匿名观察者。说白了就一句话,添加和移除要配对出现。
注册了通知观察者,咱们即可以随时随地的去post一个通知了(固然,若是闲着没事,也能够不注册观察者,post通知随便玩,只是没人理睬罢了)。NSNotificationCenter提供了三个方法来post一个通知,以下所示:
- postNotification: – postNotificationName:object: – postNotificationName:object:userInfo:
咱们能够根据须要指定通知的发送者(object)并附带一些与通知相关的信息(userInfo),固然这些发送者和userInfo能够封装在一个NSNotification对象中,由- postNotification:来发送。注意,- postNotification:的参数不能为空,不然会引起一个异常,以下所示:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSNotificationCenter postNotification:]: notification is nil'
每次post一个通知时,通知中心都会去遍历一下它的分发表,而后将通知转发给相应的观察者。
另外,通知的发送与处理是同步的,在某个地方post一个消息时,会等到全部观察者对象执行完处理操做后,才回到post的地方,继续执行后面的代码。如代码清单5所示:
代码清单5:通知的同步处理
@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:TEST_NOTIFICATION object:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil]; NSLog(@"continue"); } - (void)handleNotification:(NSNotification *)notification { NSLog(@"handle notification"); } @end
运行后输出结果是:
handle notification continue
翻了好些资料,还有两个问题始终没有明确的答案。
首先就是通知中心是如何维护观察者对象的。能够明确的是,添加观察者时,通知中心没有对观察者作retain操做,即不会使观察者的引用计数加1。那通知中心维护的是观察者的weak引用呢仍是unsafe_unretained引用呢?
我的认为多是unsafe_unretained的引用,由于咱们知道若是是weak引用,其所指的对象被释放后,这个引用会被置成nil。而实际状况是通知中心还会给这个对象发送消息,并引起一个异常。而若是向nil发送一个消息是不会致使异常的。
【很是感谢 @lv-pw,上面这个问题在《斯坦福大学公开课:iOS 7应用开发》的第5集的第57分50秒中获得了解答:确实使用的是unsafe_unretained,老师的解释是,之因此使用unsafe_unretained,而不使用weak,是为了兼容老版本的系统。】
另外,咱们知道NSNotificationCenter实现的是观察者模式,并且一般状况下消息在哪一个线程被post,就在哪一个线程被转发。而从上面的描述能够发现, -addObserverForName:object:queue:usingBlock:添加的匿名观察者能够在指定的队列中处理通知,那它的实现机制是什么呢?
在咱们的应用程序中,一个大的话题就是两个对象之间如何通讯。咱们须要根据对象之间的关系来肯定采用哪种通讯方式。对象之间的通讯方式主要有如下几种:
通常状况下,咱们能够根据如下两点来肯定使用哪一种方式:
Objective-C中的通知因为其广播性及松耦合性,很是适合于大的范围内对象之间的通讯(模块与模块,或一些框架层级)。通知使用起来很是方便,也正由于如此,因此容易致使滥用。因此在使用前仍是须要多想一想,是否有更好的方法来实现咱们所须要的对象间通讯。毕竟,通知机制会在必定程度上会影响到程序的性能。
对于使用NSNotificationCenter,最后总结一些小建议: