iOS NSNotificationCenter與自定義通知的封裝(PSSNotificationCenter)
前言
作為iOS開發者,大家應該都使用過系統通知(NSNotificationCenter),無非就是三步,1. 註冊通知,2.傳送通知,3.銷燬觀察者,我在這裡就不多解釋了;。如果忘記銷燬觀察者,ios9之前是會崩潰的。因此我就有了自己實現全域性一對多分發通知的想法,於是封裝了PSSNotificationCenter
系統通知如何使用
通知的使用為3步:
- 註冊通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveSysNoti) name:@"target" object:nil]; - (void)receiveSysNoti:(NSNotification *)noti { NSLog(@"%@", [[NSThread currentThread] isMainThread] ? @"收到系統通知:主執行緒" : @"收到系統通知:子執行緒"); }
- 在需要的時候傳送通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"target" object:nil];
- 移出觀察者
如果iOS9之前,不移除觀察者是會崩潰的,而且崩潰不會有斷點,如果你繼承了友盟,甚至不會給你列印崩潰資訊,很難找,所以務必要記得。
// 移除目標所有通知 [[NSNotificationCenter defaultCenter] removeObserver:self]; // 移除目標對應通知 [[NSNotificationCenter defaultCenter] removeObserver:self name:@"target" object:nil];
系統通知還可以使用block的方式:
但是使用block方式會有記憶體問題,即便用上面兩個方法移除,block本身依舊被NotificationCenter持有,當傳送通知時依然會執行;請看程式碼
// 註冊通知 [[NSNotificationCenter defaultCenter] addObserverForName:@"block" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { NSLog(@"block方式受到系統通知"); }]; // 點選登出通知 - (IBAction)click3:(id)sender { // [[NSNotificationCenter defaultCenter] removeObserver:self]; [[NSNotificationCenter defaultCenter] removeObserver:self name:@"block" object:nil]; } // 點擊發送通知 - (IBAction)clickSysNoti:(id)sender { [[NSNotificationCenter defaultCenter] postNotificationName:@"block" object:nil]; }
上述程式碼,首先點擊發送通知,控制檯會列印block方式受到系統通
,然後點選登出通知,再點擊發送通知,依然會列印;如果退出當前控制器,並且再進入一次控制器,點擊發送通知,控制器會列印2次,證明block沒有被銷燬(知道怎麼正確登出的同學歡迎留言告知)
那麼為什麼會出現這種情況呢?
首先,在使用block方式註冊通知的時候,我們只是傳了block
,並沒有傳Observer監聽者
,NSNotificationCenter
直接持有block
,而我們用的兩個登出方式都是針對target-action
方式的;我並沒有找到block方式的通知的登出方法,所以如果你使用通知,我建議使用target-action
方式,或者用我封裝的PSSNotificationCenter
系統通知的原理,以及出現的常見問題
使用target-action
方式的原理大概是這個樣子的:
在ios9之前,有些類是不支援weak
指標的,所以使用的是unsafe_unretained
型別的指標;顧名思義,unsafe_unretained
的意思就是不安全的,不會對物件持有的指標
,weak
所指向的地址被登出時,指標自動指向nil
,向nil
指標傳送訊息是不會崩潰的;而unsafe_unretained
指標指向的記憶體被銷燬時,是不會指向nil
的,從而導致野指標,所以ios9之前是需要登出的;
當你呼叫登出方法時,NSNotificationCenter會把指標指向nil
,避免出現野指標
記憶體大概是這個樣子的
NSNotificationCenter 內部根據每個通知名稱name
都引用著一個數組(這個陣列應該是弱引用陣列NSHashTable
),數組裡存著每個註冊者的target-action
,當發通知時([[NSNotificationCenter defaultCenter] postNotificationName:@"target" object:nil];
),會根據NotificationName
找到對應的陣列,然後便利數組裡的target-action
,達到批量分發的功能。
使用block方式大概是這個樣子的:
註冊通知時,不需要你傳Observer,NSNotificationCenter
根據NotificationName
持有一個數組,然後把block
放到這個陣列中,當你呼叫postNotification
時,根據NotificationName
找到對應陣列並遍歷呼叫;(但是會出現block不會被釋放的問題)
子執行緒傳送通知時:
在傳送時都是遍歷target-action
或block
呼叫方法,因此,在子執行緒傳送通知,方法呼叫也是發生在子執行緒,也就是說,接收也是預設在子執行緒的,所以如果你需要在接收通知時重新整理UI,建議跳轉到主執行緒哦。
target-action
方式需要宣告單獨的方法接收,如果支援ios9之前的版本還得登出,真是好麻煩。block
方式呢,還會出現記憶體問題,API提供的較少,因此我決定,封裝一款自己的通知 - PSSNotificationCenter
PSSNotificationCenter 自己封裝的全域性通知
封裝這個框架,首先,不能存在記憶體問題;其次,用法必須簡單容易理解。因此我選擇block方式進行封裝;
- 使用方法:不需要登出,也不存在記憶體問題,子執行緒發通知也會自動跳轉到主執行緒接收通知,是執行緒安全的。
// 註冊通知
// 不帶NotificationName,字用預設的name kDefaultNotificationName。observer必須傳,而且要是NSObject
[[PSSNotificationCenter defaultCenter] addEvent:^(id info) {
NSLog(@"%@", info);
} observer:self.obj_1];
[[PSSNotificationCenter defaultCenter] addEvent:^(id info) {
NSLog(@"%@", info);
} observer:self];
[[PSSNotificationCenter defaultCenter] addEvent:^(id info) {
} eventName:@"PSS" observer:self];
// 傳送通知
[[PSSNotificationCenter defaultCenter] postDefaultNotification:@"11111"];
[[PSSNotificationCenter defaultCenter] postNotificationByName:@"PSS" info:@"附帶資訊"];
實現原理及原始碼
就像系統的NSNotificationCenter一樣,我們也需要一個單利PSSNotificationCenter
,然後說一說我們記憶體方案:
上圖中的EventSet是通過執行時動態給VC1(也可以說是監聽者,NSObject型別)新增的屬性
PSSNotificationCenter下有一個Dictionary,結構大致如下:
@property (nonatomic, strong) NSMutableDictionary <NSString *, NSMapTable<NSString *, PSSEventSet *> *> *eventDict;
@{
@"通知的名字_1": @{ // 這個字典是NSMapTable,可以對持有的Value弱引用
@"觀察者記憶體地址生成的字串_1": PSSEventSet物件,
@"觀察者記憶體地址生成的字串_2": PSSEventSet物件,
},
@"通知的名字_1": @{ // 這個字典是NSMapTable
@"觀察者記憶體地址生成的字串_1": PSSEventSet物件,
@"觀察者記憶體地址生成的字串_2": PSSEventSet物件,
},
};
NSMapTable: 類似於字典,可以對value進行弱引用;
PSSEventSet: 動態新增給觀察者的屬性
@interface PSSEventSet : NSObject
@property (nonatomic, strong) NSMutableArray<PSSBlockObject *> *blockObjectArray;
@end
PSSBlockObject: 用於持有block
typedef void(^PSSNotiEvent)(id info);
@interface PSSBlockObject : NSObject
@property (nonatomic, copy) PSSNotiEvent eventHandler;
@end
** .h中暴露的方法 **
#define kDefaultNotificationName @"PSSDefaultNotification"
@class PSSEventSet;
@interface PSSNotificationCenter : NSObject
+ (instancetype)defaultCenter;
- (void)addEvent:(PSSNotiEvent)event observer:(NSObject *)observer;
- (void)addEvent:(PSSNotiEvent)event eventName:(NSString *)eventName observer:(NSObject *)observer;
/// info: 傳值
- (void)postNotificationByName:(NSString *)name info:(id)info;
- (void)postDefaultNotification:(id)info;
/// 移出對應通知事件
- (void)removeNotificationName:(NSString *)name;
/// 移出所有通知下的 observer對應的事件(不給此observer傳送事件了)
- (void)removeObserver:(NSObject *)observer;
- (void)removeObserverByEventSet:(PSSEventSet *)eventSet;
/// 移出對應通知下,對應observer的事件
- (void)removeNotificationName:(NSString *)name observer:(NSObject *)observer;
/// 移出所有事件
- (void)removeAllNoti;
@end
覺得有用給個star唄