簡述
NSNotification 是iOS中一個消息通知類抬旺,存儲消息的一些信息崭倘;
NSNotificationCenter 是一個通知中心问芬,采用單例設(shè)計模式悦析,用來發(fā)布、接收等消息操作的類此衅。
NSNotification介紹
首先來看下强戴,NSNotification類可以存儲哪些消息信息亭螟。
// 消息的名稱,操作對應(yīng)的消息的依據(jù)骑歹,只讀
@property (readonly, copy) NSNotificationName name;
// 消息對象预烙,只讀
@property (nullable, readonly, retain) id object;
// 存儲消息信息的字典,只讀
@property (nullable, readonly, copy) NSDictionary *userInfo;
從上面可以看出道媚,NSNotification 類使用一個 userInfo
字典來存儲消息信息的扁掸,并使用一個name字符串來標(biāo)識消息。
如何創(chuàng)建一個消息呢最域?
實例方法:
// 參數(shù)分別為:消息名稱谴分、對象、消息字典
- (instancetype)initWithName:(NSNotificationName)name object:(nullable id)object userInfo:(nullable NSDictionary *)userInfo;
系統(tǒng)后來又使用分類新增了兩種快捷獲取消息的類方法
+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject;
+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
使用上面兩種類方法可以省去我們手動分配空間的操作镀脂,可以根據(jù)業(yè)務(wù)需求選擇其一即可牺蹄;
注:系統(tǒng)明確指出不要調(diào)用 init 方法來初始化一個消息對象,而且NSNotification的屬性name薄翅、object沙兰、userInfo都被設(shè)計為只讀的,可能是出于消息安全考慮吧
NSNotificationCenter介紹
假如我們使用NSNotification類創(chuàng)建了一條消息翘魄,接下來如何發(fā)送該消息呢鼎天?這時就需要具有發(fā)布通知功能的類NSNotificationCenter(即通知中心)了。
注:在發(fā)布消息前暑竟,我們首先需要向通知中心一個注冊一個消息監(jiān)聽者來接收我們將要發(fā)布的消息
首先獲取通知中心
// [NSNotificationCenter defaultCenter]
@property (class, readonly, strong) NSNotificationCenter *defaultCenter;
向該通知中心注冊一個監(jiān)聽者斋射。
// 最常用的注冊通知方法
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
// 注冊通知的block形式
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block;
如果我們創(chuàng)建好了一個通知 notification,就可以使用下面的方法發(fā)送通知光羞。
- (void)postNotification:(NSNotification *)notification;
如果你并沒有一個消息對象绩鸣,你也可以在發(fā)布消息時直接創(chuàng)建。
// 只傳遞消息
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
// 數(shù)據(jù)字典一并發(fā)布
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
注:NSNotificationCenter 實現(xiàn)的過程實質(zhì)是:某個對象向NSNotificationCenter注冊需要接收某種消息纱兑,NSNotificationCenter便保存了這個對象的內(nèi)存地址呀闻,當(dāng)發(fā)布消息時,NSNotificationCenter就向該內(nèi)存地址發(fā)送消息潜慎;那么假如這個對象內(nèi)存已經(jīng)被釋放捡多,即成為了僵尸對象,指向該地址的指針也就成了野指針铐炫,這時候當(dāng)NSNotificationCenter將消息發(fā)送給該地址時垒手,就會出現(xiàn)crash情況。這是在某個類使用通知的情況倒信,但是在控制器中卻不會crash科贬,原因是控制器在調(diào)用dealloc時,會默默的幫我們移除通知。
但是榜掌,為了規(guī)范优妙,一個addObserver就應(yīng)該對應(yīng)一個removeObserver。
移除觀察者
// 移除observer上多有的注冊
- (void)removeObserver:(id)observer;
// 移除指定的消息類型
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;
使用通知
使用通知比較簡單一共分三步
步驟一:注冊通知憎账,新增消息類型以及接收者
步驟二:發(fā)送通知
步驟三:移除通知套硼,即移除消息接收者
過程可以理解為:當(dāng)某些個對象需要某種類型的消息時,便向NSNotificationCenter通知中心注冊胞皱,NSNotificationCenter便將該消息和該對象綁定(保存該對象的內(nèi)存地址)邪意,合適時機(jī),NSNotificationCenter便發(fā)布消息反砌,注冊對象根據(jù)消息消息類型(name)選擇性接收(name相同才接收)雾鬼,當(dāng)注冊對象內(nèi)存被釋放時,需要手動移除NSNotificationCenter中對應(yīng)的注冊對象于颖。
應(yīng)用情景:頁面跳轉(zhuǎn)時呆贿,我們給下一個頁面發(fā)送一條消息
當(dāng)前頁面
- (void)viewDidLoad {
[super viewDidLoad];
// 對象參數(shù)
UIButton *btn = [UIButton new];
[btn setTitle:@"btn title" forState:UIControlStateNormal];
// 信息字典
NSDictionary *dic = @{@"name":@"lolita0164"};
// 創(chuàng)建一則消息
NSNotification *notification = [[NSNotification alloc] initWithName:@"lolita0164" object:btn userInfo:dic];
// 模擬業(yè)務(wù)嚷兔,延遲5秒發(fā)布消息
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 發(fā)布消息
[[NSNotificationCenter defaultCenter] postNotification:notification];
});
}
下一個頁面 NextPageViewController.h
- (void)viewDidLoad {
[super viewDidLoad];
// 注冊消息
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(noti:) name:@"lolita0164" object:nil];
}
// 消息處理
-(void)noti:(NSNotification *)noti{
NSLog(@"%@接收到的消息:%@",[self class],noti.userInfo);
UIButton *btn = noti.object;
NSLog(@"%@接收到的消息:%@",[self class],btn.titleLabel.text);
}
// 對象釋放時森渐,移除該注冊對象
-(void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"lolita0164" object:nil];
NSLog(@"%@釋放了",[self class]);
}
運(yùn)行結(jié)果
消息線程的選擇
NSNotificationCenter消息的接受線程是基于發(fā)送消息的線程的,因此冒晰,有時候你發(fā)送的消息有時候可能不在主線程同衣,而大家都知道操作UI必須在主線程,所以壶运,在你收到消息通知的時候耐齐,注意選擇你要執(zhí)行的線程。
比如對于需要更新UI的通知蒋情,我們在主線程中更新UI
發(fā)送的消息非主線程中
dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(defaultQueue, ^{
[[NSNotificationCenter defaultCenter] postNotificationName:@"UIUpdate" object:nil];
});
- (void)UIUpdate{
if ([[NSThread currentThread] isMainThread]) {
NSLog(@"main");
} else {
NSLog(@"not main");
}
// 主線程中更新UI
dispatch_async(dispatch_get_main_queue(), ^{
//更新UI操作
});
}
輸出結(jié)果:
來說下注冊通知的block形式的使用埠况,該方法讓我們可以直接指定什么線程去完成消息處理。
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block;
示例
// 注冊并接收處理消息
self.observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"lolita0164" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
NSLog(@"%@接收到的消息:%@",[self class],note.userInfo);
UIButton *btn = note.object;
NSLog(@"%@接收到的消息:%@",[self class],btn.titleLabel.text);
}];
如果參數(shù)隊列不指定棵癣,為nil辕翰,則表示和發(fā)送消息的線程一致。
注銷操作
[[NSNotificationCenter defaultCenter] removeObserver:self.observer];
self.observer = nil; // 將該對象一并注銷
注意事項
1狈谊、注冊通知必須在發(fā)送通知之前
2喜命、注冊和發(fā)布消息的類型name要一致
3、消息接收對象必須存在河劝,若不存在,則需要手動移除該消息接收對象
4、viewDidLoad里注冊赞哗,dealloc中移除融柬;或者viewWillAppear:里注冊,viewWillDisappear:移除
5务甥、移除通知最好指定消息類型name牡辽,否則可能移除掉其他對其他消息的監(jiān)聽
擴(kuò)展
1贪染、和KVO對比?
- 兩者都是屬于觀察者設(shè)計模式催享,但是通知更靈活杭隙,適用更廣,比起KVO因妙,通知可以用來監(jiān)聽狀態(tài)的變化痰憎、鍵盤出現(xiàn)、app前后臺的出現(xiàn)等攀涵,傳遞的數(shù)據(jù)也多種铣耘,不僅可以傳遞字典參數(shù)還可以傳遞對象參數(shù)
- KVO通常用來監(jiān)聽某屬性值的變化,它可以記錄新舊值
- 兩者都是MVC模式下以故,實現(xiàn)UI和數(shù)據(jù)分離的手段
2蜗细、和delegate對比?
- 通知使用起來更簡潔怒详,邏輯清晰
- 通知適用于一對多的情況炉媒,它會給每一個存在的對象發(fā)布消息,這種廣播式的發(fā)送消息意味著開銷的增大
- delegate適用于一對一的情況昆烁,明確知道delegate是誰的代理吊骤,而又是誰去實現(xiàn)代理協(xié)議
- 兩者的使用視情況而定,都是實現(xiàn)解耦的解決方案