场景
-
在
macOS
下进行Cocoa
开发时,我们偶尔会需要监听窗口的大小改变的通知,以便能控制子窗口或者NSView
改变大小. 比如NSWindowDidResizeNotification
通知. 那么我们应该如何监听父窗口的大小改变通知? -
如果我们使用
NSNotificationCenter
进行postNotificationName
消息, 这个消息发送到接受者是异步还是同步的?
说明
-
Cocoa
包括两种类型的通知中心.NSNotificationCenter
管理同进程的通知, 而NSDistributedNotificationCenter
管理在同一个系统里的跨进程的通知. -
这里我们开发界面程序,在同一个进程里的通知都是通过
NSNotificationCenter
管理. 每个进程都有一个默认的通知中心,这个通知中心你可以通过[NSNotificationCenter defaultCenter]
类方法来获取. 这个通知中心处理同一个进程里通知. 这个通知中心的设计模式就是我们常听说的观察者模式
或者说是发布订阅模式
. 我在这篇文章里有说观察者模式在项目中实际使用例子2 -
NSNotificationCenter
使用通知有以下注意点:- 通知中心发送通知给观察者是同步的. 即当
postNotificationName
发布一个通知调用,控制器(通知中心)不会返回,直到所有的观察者已经接收并处理这些消息. 如果想发送异步通知,那么需要使用一个通知队列NSNotificationQueue
, 这里不做细讲. - 在多线程程序, 通知往往可以在某个线程发出,而和观察者注册通知处理方法的线程不一样是可以的。
- 每一个线程都有
默认的通知队列(notification queue)
,它关联着进程的通知中心。这个队列用来维护通知实例NSNotification
。 这个通知队列遵守FIFO
顺序,依次把通知转发给通知中心,而通知中心把这个通知发送给注册的观察者. - 如果工作线程(非界面线程) 发出一个通知给通知中心,而观察者的处理方法却调用了界面绘制相关操作,比如设置文本值,那么可能会导致程序崩溃,因为界面元素需又界面线程操作,所以接收的通知是在界面操作,那么这个
postNotificationName
必须要在界面线程调用(即主线程).
- 通知中心发送通知给观察者是同步的. 即当
-
使用完通知要把它从通知中心移除,不然这个通知会一直存在造成以下问题:
- 无效的通知中心观察者存在内存泄漏.
- 外部如果发送未被移除的通知, 而观察者已经销毁,那么程序会崩溃.
-
观察者必须是一个
Object-C
对象, 以下addObserver
方法必须有值,不能是nil
.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveMessage:) name:kBookOpenNotification object:nil];
例子
- 以下是一个命令行程序演示了
NSNotificationCenter
最常用的同步发送通知.
//
// main.m
// test-notification
//
// Created by sai on 5/2/20.
// Copyright (c) 2020 tobey. All rights reserved.
//
#import <Foundation/Foundation.h>
static NSString* kBookCancelNotification = @"BookCancelNotification";
static NSString* kBookOpenNotification = @"BookOpenNotification";
static BOOL shouldKeepRunning = NO;
@interface Book: NSObject
@end
@implementation Book
-(void)receiveMessage:(NSNotification*)data
{
NSDictionary* dic = data.object;
NSLog(@"%@ data is %@",data.name,dic);
}
-(id)init
{
NSLog(@"Book init");
self = [super init];
// object 为nil时,接收任何位置发的通知.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveMessage:) name:kBookCancelNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveMessage:) name:kBookOpenNotification object:nil];
return self;
}
-(void)dealloc
{
NSLog(@"Book dealloc");
// 使用完通知要把它从通知中心移除,不然这个通知会一直存在造成以下问题:
// 1.无效的通知中心观察者存在内存泄漏.
// 2.外部如果发送 kBookCancelNotification 消息,而BOOK已经销毁,那么程序会崩溃.
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
@end
void testNotification()
{
NSLog(@"================ testNotification BEGIN ================");
@autoreleasepool {
Book* book = [Book new];
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:@"https://blog.csdn.net/infoworld",@"blog",nil];
NSLog(@"== postNotificationName kBookCancelNotification ==");
[[NSNotificationCenter defaultCenter] postNotificationName:kBookCancelNotification object:dict userInfo:nil];
NSDictionary* dict1 = [NSDictionary dictionaryWithObjectsAndKeys:@"Tobey",@"name",nil];
NSLog(@"== postNotificationName kBookOpenNotification ==");
[[NSNotificationCenter defaultCenter] postNotificationName:kBookOpenNotification object:dict1 userInfo:nil];
}
// 1.如果Book dealloc 里没有调用 removeObserver,那么以下代码调用即会崩溃.
// 2.如果Book dealloc 里调用了removeObserver,那么消息中心没有对应的处理观察者,会丢弃这个通知.
NSDictionary* dict2 = [NSDictionary dictionaryWithObjectsAndKeys:@"Tobey",@"name",nil];
[[NSNotificationCenter defaultCenter] postNotificationName:kBookOpenNotification object:dict2 userInfo:nil];
NSLog(@"================ testNotification END ================");
}
int main(int argc, const char * argv[])
{
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
testNotification();
dispatch_async(dispatch_get_main_queue(),^(){
shouldKeepRunning = NO;
});
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
}
return 0;
}
输出
2020-05-02 23:48:52.950 test-notification[896:303] Hello, World!
2020-05-02 23:48:52.952 test-notification[896:303] ================ testNotification BEGIN ================
2020-05-02 23:48:52.953 test-notification[896:303] Book init
2020-05-02 23:48:52.953 test-notification[896:303] == postNotificationName kBookCancelNotification ==
2020-05-02 23:48:52.954 test-notification[896:303] BookCancelNotification data is {
blog = "https://blog.csdn.net/infoworld";
}
2020-05-02 23:48:52.954 test-notification[896:303] == postNotificationName kBookOpenNotification ==
2020-05-02 23:48:52.955 test-notification[896:303] BookOpenNotification data is {
name = Tobey;
}
2020-05-02 23:48:52.955 test-notification[896:303] Book dealloc
2020-05-02 23:48:52.956 test-notification[896:303] ================ testNotification END ================