一、添加通知監聽者的方式
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block ;
以上是添加通知監聽者的兩種方式
1. 添加監聽者時的name與object
兩種方式添加監聽者傳入的參數都包含name和object,這兩個參數都可以為空,關于這兩個參數主要有以下四種情況:
1.1 若是name和object同時為空則可以監聽程序中所有的通知
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receviedNoti:) name:nil object:nil];
[[NSNotificationCenter defaultCenter] addObserverForName:nil object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
NSLog(@"block---%@",note);
}];
}
- (void)receviedNoti:(NSNotification *)noti {
NSLog(@"selector---%@",noti);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[[NSNotificationCenter defaultCenter] postNotificationName:@"noti_one" object:nil userInfo:@{@"ncKey":@"ncValue"}];
}
添加了兩個監聽者,name與object都為空
2018-11-22 16:52:46.457900+0800 Nunca[50654:7785885] selector---NSConcreteNotification 0x60000025ae20 {name = UIViewAnimationDidStopNotification; object = <UIViewAnimationState: 0x7f9f76603fe0>; userInfo = {
delegate = "<UIViewAnimationBlockDelegate: 0x60400047ddc0>";
name = "";
}}
2018-11-22 16:52:46.458099+0800 Nunca[50654:7785885] block---NSConcreteNotification 0x60000025ae20 {name = UIViewAnimationDidStopNotification; object = <UIViewAnimationState: 0x7f9f76603fe0>; userInfo = {
delegate = "<UIViewAnimationBlockDelegate: 0x60400047ddc0>";
name = "";
}}
2018-11-22 16:53:03.266696+0800 Nunca[50654:7785885] selector---NSConcreteNotification 0x60400025e2d0 {name = _UIWindowSystemGestureStateChangedNotification; object = <UIWindow: 0x7f9f765074d0; frame = (0 0; 375 667); gestureRecognizers = <NSArray: 0x6040002500b0>; layer = <UIWindowLayer: 0x60400022a260>>; userInfo = {
"_UIWindowSystemGestureCancellingTouchesUserInfoKey" = 0;
}}
2018-11-22 16:53:03.266918+0800 Nunca[50654:7785885] block---NSConcreteNotification 0x60400025e2d0 {name = _UIWindowSystemGestureStateChangedNotification; object = <UIWindow: 0x7f9f765074d0; frame = (0 0; 375 667); gestureRecognizers = <NSArray: 0x6040002500b0>; layer = <UIWindowLayer: 0x60400022a260>>; userInfo = {
"_UIWindowSystemGestureCancellingTouchesUserInfoKey" = 0;
}}
2018-11-22 16:53:03.267779+0800 Nunca[50654:7785885] selector---NSConcreteNotification 0x60400025d610 {name = noti_one; userInfo = {
ncKey = ncValue;
}}
2018-11-22 16:53:03.267996+0800 Nunca[50654:7785885] block---NSConcreteNotification 0x60400025d610 {name = noti_one; userInfo = {
ncKey = ncValue;
}}
監聽到了程序中所有發出來的通知,上面截取了一部分,最后打印的是點擊事件中自己添加的。
1.2 若是name不為空、object為空則可以監聽所有發送時通知名稱與name相同的通知
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receviedNoti:) name:@"nc_noti_one" object:nil];
[[NSNotificationCenter defaultCenter] addObserverForName:@"nc_noti_one" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
NSLog(@"block---%@",note);
}];
}
- (void)receviedNoti:(NSNotification *)noti {
NSLog(@"selector---%@",noti);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[[NSNotificationCenter defaultCenter] postNotificationName:@"nc_noti_one" object:nil userInfo:@{@"ncKey_1":@"ncValue_1"}];
[[NSNotificationCenter defaultCenter] postNotificationName:@"nc_noti_one" object:@"123" userInfo:@{@"ncKey_2":@"ncValue_2"}];
}
添加兩個監聽者,監聽的name相同,object都為空,在點擊屏幕時發送兩條name相同的通知,但是一條object為空,一條不為空
2018-11-22 17:05:38.920806+0800 Nunca[50859:7801142] selector---NSConcreteNotification 0x604000253470 {name = nc_noti_one; userInfo = {
"ncKey_1" = "ncValue_1";
}}
2018-11-22 17:05:38.921132+0800 Nunca[50859:7801142] block---NSConcreteNotification 0x604000253470 {name = nc_noti_one; userInfo = {
"ncKey_1" = "ncValue_1";
}}
2018-11-22 17:05:38.921327+0800 Nunca[50859:7801142] selector---NSConcreteNotification 0x600000248610 {name = nc_noti_one; object = 123; userInfo = {
"ncKey_2" = "ncValue_2";
}}
2018-11-22 17:05:38.921576+0800 Nunca[50859:7801142] block---NSConcreteNotification 0x600000248610 {name = nc_noti_one; object = 123; userInfo = {
"ncKey_2" = "ncValue_2";
}}
監聽者name不為空、object為空時,不管發送通知時有沒有傳入object,只要name對上了,就能被成功監聽。
1.3 若是name為空、object不為空則可以監聽所有發送通知時傳入的object與監聽傳入的object為同一個對象的通知
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receviedNoti:) name:nil object:self];
[[NSNotificationCenter defaultCenter] addObserverForName:nil object:@"123" queue:nil usingBlock:^(NSNotification * _Nonnull note) {
NSLog(@"block---%@",note);
}];
}
- (void)receviedNoti:(NSNotification *)noti {
NSLog(@"selector---%@",noti);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[[NSNotificationCenter defaultCenter] postNotificationName:@"noti_name1" object:self userInfo:@{@"ncKey_1":@"ncValue_1"}];
[[NSNotificationCenter defaultCenter] postNotificationName:@"noti_name2" object:@"123" userInfo:@{@"ncKey_2":@"ncValue_2"}];
}
添加了兩個name為空的監聽者,object一個為當前控制器,一個為字符串123
發送通知時name不可以為空
2018-11-22 17:19:32.028685+0800 Nunca[51024:7816220] selector---NSConcreteNotification 0x604000447440 {name = noti_name1; object = <NcNotificationVCtr: 0x7f9bd351bcc0>; userInfo = {
"ncKey_1" = "ncValue_1";
}}
2018-11-22 17:19:32.028979+0800 Nunca[51024:7816220] block---NSConcreteNotification 0x604000447440 {name = noti_name2; object = 123; userInfo = {
"ncKey_2" = "ncValue_2";
}}
監聽者name為空、object不為空時,只要發送通知時傳入的object是與監聽者的相同、不管發送通知的name是什么通知都能被成功監聽。
1.4 若name與object都不為空,則只有發送時通知名稱與name相同并且傳入的object與監聽傳入的object為同一個對象的通知能被成功監聽
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receviedNoti:) name:@"noti_name" object:self];
[[NSNotificationCenter defaultCenter] addObserverForName:@"noti_name" object:@"123" queue:nil usingBlock:^(NSNotification * _Nonnull note) {
NSLog(@"block---%@",note);
}];
}
- (void)receviedNoti:(NSNotification *)noti {
NSLog(@"selector---%@",noti);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[[NSNotificationCenter defaultCenter] postNotificationName:@"noti_name" object:self userInfo:@{@"ncKey_1":@"ncValue_1"}];
[[NSNotificationCenter defaultCenter] postNotificationName:@"noti_name" object:@"123" userInfo:@{@"ncKey_2":@"ncValue_2"}];
[[NSNotificationCenter defaultCenter] postNotificationName:@"noti_name" object:nil userInfo:@{@"ncKey_3":@"ncValue_3"}];
[[NSNotificationCenter defaultCenter] postNotificationName:@"noti_name_x" object:self userInfo:@{@"ncKey_4":@"ncValue_4"}];
}
若監聽者的name與object都不為空,那只有發送通知時傳入的name與object都與監聽者的相同通知才能被成功監聽。
2 通知發送與監聽的線程
回到以上兩種添加監聽者的方法,上面列出來的是它們的一些共性,那他們的差異也是存在的。
2.1 queue為nil的時候
對于第一種傳入selector方式,通知在哪個線程發送則在哪個線程被同步回掉。
對于第二種傳入block形式的,可以傳入一個NSOperationQueue,指定block被加入至哪個隊列,若穿入的是空,則block會被在通知發送時所在線程同步調用(與第一種方式相同了)。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(@"--add observer in thread: %@",[NSThread currentThread]);
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receviedNoti:) name:@"noti_name" object:nil];
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"noti_name" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
NSLog(@"--block call back in thread: %@",[NSThread currentThread]);
}];
self.notiThread = [[NSThread alloc] initWithTarget:self selector:@selector(notiThreadStart) object:nil];
[self.notiThread start];
}
- (void)receviedNoti:(NSNotification *)noti {
NSLog(@"--selector call back in thread: %@",[NSThread currentThread]);
}
- (void)notiThreadStart {
[[NSRunLoop currentRunLoop] addPort:[NSPort new] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self performSelector:@selector(notiThreadMethod) onThread:self.notiThread withObject:nil waitUntilDone:NO];
}
- (void)notiThreadMethod {
NSLog(@"--post Noti in thread: %@",[NSThread currentThread]);
[[NSNotificationCenter defaultCenter] postNotificationName:@"noti_name" object:self userInfo:nil];
NSLog(@"--finish");
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver: _observer];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
在主線程添加了通知監聽者(以第二種方式添加的監聽者傳入的queue為nil),然后在子線程發送通知。
另外可以看到第一種方式添加監聽者時,監聽者是我們自己傳入指定的,第二種方式則是創建并返回了一個監聽者,因此第二種需要我們額外保存返回的observer,以便于在何時的時機移除。
2018-11-22 18:10:29.882628+0800 Nunca[51489:7869385] --add observer in thread: <NSThread: 0x600000065e40>{number = 1, name = main}
2018-11-22 18:10:35.204503+0800 Nunca[51489:7869593] --post Noti in thread: <NSThread: 0x604000470c00>{number = 3, name = (null)}
2018-11-22 18:10:35.205316+0800 Nunca[51489:7869593] --selector call back in thread: <NSThread: 0x604000470c00>{number = 3, name = (null)}
2018-11-22 18:10:35.205598+0800 Nunca[51489:7869593] --block call back in thread: <NSThread: 0x604000470c00>{number = 3, name = (null)}
2018-11-22 18:10:35.205969+0800 Nunca[51489:7869593] --finish
可以看到,監聽者接收到通知執行監聽方法時所在的線程與通知發送所在線程一致,與添加監聽者時所在的線程無關。另外,在通知發送后先去執行了監聽者的方法之后再繼續執行當前代碼(打印--finish),說明通知的發送與監聽是同步執行的。
2.2 指定queue的時候
對于第二種添加監聽者的方式,也可以指定執行block所在queue,當監聽到通知后要更新UI的時候就適合以這種方式添加監聽者,此時將queue設置為mainQueue就好。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(@"--add observer in thread: %@",[NSThread currentThread]);
[[NSNotificationCenter defaultCenter] addObserverForName:@"noti_name" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
NSLog(@"--block call back in thread: %@",[NSThread currentThread]);
for (int i = 0; i < 500; i++) {
NSLog(@"--block--%d",i);
}
}];
self.notiThread = [[NSThread alloc] initWithTarget:self selector:@selector(notiThreadStart) object:nil];
[self.notiThread start];
}
- (void)notiThreadStart {
[[NSRunLoop currentRunLoop] addPort:[NSPort new] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self performSelector:@selector(notiThreadMethod) onThread:self.notiThread withObject:nil waitUntilDone:NO];
}
- (void)notiThreadMethod {
NSLog(@"--post Noti in thread: %@",[NSThread currentThread]);
[[NSNotificationCenter defaultCenter] postNotificationName:@"noti_name" object:self userInfo:nil];
NSLog(@"--finish");
}
將queue指定為mainQueue
2018-11-22 18:30:39.598772+0800 Nunca[51794:7888240] --add observer in thread: <NSThread: 0x600000071380>{number = 1, name = main}
2018-11-22 18:30:41.612623+0800 Nunca[51794:7888393] --post Noti in thread: <NSThread: 0x60000027cac0>{number = 3, name = (null)}
2018-11-22 18:30:41.613826+0800 Nunca[51794:7888313] --block call back in thread: <NSThread: 0x600000071380>{number = 1, name = main}
2018-11-22 18:30:41.614457+0800 Nunca[51794:7888313] --block--0
2018-11-22 18:30:41.614660+0800 Nunca[51794:7888313] --block--1
2018-11-22 18:30:41.615330+0800 Nunca[51794:7888313] --block--2
2018-11-22 18:30:41.615687+0800 Nunca[51794:7888313] --block--3
...
2018-11-22 18:30:41.827388+0800 Nunca[51794:7888313] --block--497
2018-11-22 18:30:41.827685+0800 Nunca[51794:7888313] --block--498
2018-11-22 18:30:41.828318+0800 Nunca[51794:7888313] --block--499
2018-11-22 18:30:41.828621+0800 Nunca[51794:7888393] --finish
可以看到通知雖然在子線程被發送,但監聽方法是在主線程執行,并且也是同步的,在監聽方法中的所有任務執行完成之后才會繼續原來的程序,因此也需要注意,發送通知時,很可能因為監聽者需要處理耗時任務而使得通知發送所在的線程被阻塞。
二、存儲observer的結構
首先根據GNUstep的代碼,找出幾個關鍵結構
typedef struct NCTbl {
Observation *wildcard; /* Get ALL messages. */
GSIMapTable nameless; /* Get messages for any name. */
GSIMapTable named; /* Getting named messages only. */
unsigned lockCount; /* Count recursive operations. */
NSRecursiveLock *_lock; /* Lock out other threads. */
Observation *freeList;
Observation **chunks;
unsigned numChunks;
GSIMapTable cache[CACHESIZE];
unsigned short chunkIndex;
unsigned short cacheIndex;
} NCTable;
主要關注前三個:
1.Observation *wildcard;
typedef struct Obs {
id observer; /* Object to receive message. */
SEL selector; /* Method selector. */
struct Obs *next; /* Next item in linked list. */
int retained; /* Retain count for structure. */
struct NCTbl *link; /* Pointer back to chunk table */
} Observation;
以鏈表的方式存儲observers,里面存放的observer監聽所有通知。
2.GSIMapTable nameless
struct _GSIMapTable {
NSZone *zone;
uintptr_t nodeCount; /* Number of used nodes in map. */
uintptr_t bucketCount; /* Number of buckets in map. */
GSIMapBucket buckets; /* Array of buckets. */
GSIMapNode freeNodes; /* List of unused nodes. */
uintptr_t chunkCount; /* Number of chunks in array. */
GSIMapNode *nodeChunks; /* Chunks of allocated memory. */
uintptr_t increment;
#ifdef GSI_MAP_EXTRA
GSI_MAP_EXTRA extra;
#endif
};
struct _GSIMapBucket {
uintptr_t nodeCount; /* Number of nodes in bucket. */
GSIMapNode firstNode; /* The linked list of nodes. */
};
struct _GSIMapNode {
GSIMapNode nextInBucket; /* Linked list of bucket. */
GSIMapKey key;
#if GSI_MAP_HAS_VALUE
GSIMapVal value;
#endif
};
當observer的name為空、object不為空的時候,則會被存入nameless。
以object為key,以observer的集合為value,構成一個MapNode結構,再以鏈表形式保存在nameless。
3.GSIMapTable named;
當observer的name不為空時,observer被存入named。
以observer的name為key,value為一個集合(or鏈表結構),集合中存儲的結構是以object為key、以對應的observers為value。
添加observer時的具體步驟:
1.判斷observer的name是否為空,如果不為空則進入named表,再以observer的name為key去named表中索引對應的下一層級的表,如果索引出來不存在則創建,取得下一層級的表的時候,再以observer的object為key去索引,object即使為空也可以索引到對應的集合(或者鏈表),將observer存入。
2.如果observer的name為空,則繼續判斷object是否為空,若object不為空,則以object為key去nameless表中查找對應的集合,并將observer存入
3.如果object也為空,則將observer添加至wildcard
移除observer時的具體步驟:
1.如果name與object都為空
1.1 先去wildcard去查找、刪除
1.2 再繼續步驟2與3
2.如果name為空
.......(有空再補充)
3.如果name不為空
.......(有空再補充)
三、發送通知的方式
1.直接post方式
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
這是最常見的發送通知的方式,上面的例子也全部基于此種通知發送的方式。這種方式發送的通知都是同步地去執行監聽方法。
2.NSNotificationQueue
2.1NSNotificationQueue的結構
@interface NSNotificationQueue : NSObject {
@private
id _notificationCenter;
id _asapQueue;
id _asapObs;
id _idleQueue;
id _idleObs;
}
2.2獲取NSNotificationQueue對象的方式
// 類對象 NSNotificationQueue.defaultQueue;
@property (class, readonly, strong) NSNotificationQueue *defaultQueue;
// 初始化NSNotificationQueue的指定方式
- (instancetype)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter NS_DESIGNATED_INITIALIZER;
在同一線程中通過以上兩種方式獲取到的都是同一個NSNotificationQueue對象,即使多次調用initWithNotificationCenter,實際上也都是返回的同一個,只有切換線程,獲取的才會是一個不一樣的。
(這點與GNUstep的源碼是有出路的,按照GNUstep中的實現每次initWithNotificationCenter應該都會生成一個新的queue對象,但是在Xcode9.2中運行,每次生成的都是相同的)
NSNotificationQueue *queue = [[NSNotificationQueue alloc] initWithNotificationCenter:[NSNotificationCenter defaultCenter]];
NSNotificationQueue *queue1 = [NSNotificationQueue defaultQueue];
NSNotificationQueue *queue3 = [[NSNotificationQueue alloc] initWithNotificationCenter:[NSNotificationCenter defaultCenter]];
NSNotificationQueue *queue4 = [[NSNotificationQueue alloc] initWithNotificationCenter:[NSNotificationCenter defaultCenter]];
2018-11-23 13:15:32.799013+0800 Nunca[56077:8460141] queue = <NSNotificationQueue: 0x60000044db00>
2018-11-23 13:15:32.799013+0800 Nunca[56077:8460141] queue1 = <NSNotificationQueue: 0x60000044db00>
2018-11-23 13:15:32.799673+0800 Nunca[56077:8460141] queue3 = <NSNotificationQueue: 0x60000044db00>
2018-11-23 13:15:32.800445+0800 Nunca[56077:8460141] queue4 = <NSNotificationQueue: 0x60000044db00>
每個線程存在并指存在一個與之對應的NSNotificationQueue對象。
2.3添加通知的方法
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask:(NSNotificationCoalescing)coalesceMask forModes:(nullable NSArray<NSRunLoopMode> *)modes;
將通知加入隊列中時,可以指定postingStyle、coalesceMask、modes
postingStyle
postingStyle決定通知發送的時機,有三種:
NSPostWhenIdle:空閑的時候發送
NSPostASAP:盡快發送,一般在runloop結束當前loop之后
NSPostNow :立刻發送,添加通知進隊列地同步發送 (如果指定了合并策略會先進行通知合并)
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receviedNoti:) name:@"noti_name" object:nil];
}
- (void)receviedNoti:(NSNotification *)noti {
NSLog(@"noti==%@",noti);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSNotificationQueue *queue = [[NSNotificationQueue alloc] initWithNotificationCenter:[NSNotificationCenter defaultCenter]];
NSNotification *noti1 = [[NSNotification alloc] initWithName:@"noti_name" object:nil userInfo:@{@"noti":@"1"}];
NSNotification *noti2 = [[NSNotification alloc] initWithName:@"noti_name" object:@"123" userInfo:@{@"noti":@"2"}];
NSNotification *noti3 = [[NSNotification alloc] initWithName:@"noti_name" object:@"123" userInfo:@{@"noti":@"3"}];
NSNotification *noti4 = [[NSNotification alloc] initWithName:@"noti_name" object:nil userInfo:@{@"noti":@"4"}];
NSLog(@"enqueue noti1");
[queue enqueueNotification:noti1 postingStyle:NSPostWhenIdle coalesceMask:NSNotificationNoCoalescing forModes:nil];
NSLog(@"enqueue noti2");
[queue enqueueNotification:noti2 postingStyle:NSPostASAP coalesceMask:NSNotificationNoCoalescing forModes:nil];
NSLog(@"enqueue noti3");
[queue enqueueNotification:noti3 postingStyle:NSPostNow coalesceMask:NSNotificationNoCoalescing forModes:@[@"123"]];
NSLog(@"enqueue noti4");
[queue enqueueNotification:noti4 postingStyle:NSPostNow coalesceMask:NSNotificationNoCoalescing forModes:@[NSRunLoopCommonModes]];
}
2018-11-23 14:32:04.065704+0800 Nunca[57044:8578090] enqueue noti1
2018-11-23 14:32:04.066063+0800 Nunca[57044:8578090] enqueue noti2
2018-11-23 14:32:04.066166+0800 Nunca[57044:8578090] enqueue noti3
2018-11-23 14:32:04.067676+0800 Nunca[57044:8578090] noti==NSConcreteNotification 0x600000448c10 {name = noti_name; object = 123; userInfo = {
noti = 3;
}}
2018-11-23 14:32:04.067813+0800 Nunca[57044:8578090] enqueue noti4
2018-11-23 14:32:04.067985+0800 Nunca[57044:8578090] noti==NSConcreteNotification 0x6000004491e0 {name = noti_name; userInfo = {
noti = 4;
}}
2018-11-23 14:32:04.068216+0800 Nunca[57044:8578090] noti==NSConcreteNotification 0x6000004490c0 {name = noti_name; object = 123; userInfo = {
noti = 2;
}}
2018-11-23 14:32:04.068749+0800 Nunca[57044:8578090] noti==NSConcreteNotification 0x600000448f10 {name = noti_name; userInfo = {
noti = 1;
}}
coalesceMask
coalesceMask提供了合并通知的功能,是NS_OPTIONS類型:
NSNotificationNoCoalescing :不合并
NSNotificationCoalescingOnName :將加入新通知之前已經存在queue中的name與新通知相同的通知合并(清除)
NSNotificationCoalescingOnSender :將queue中object與當前通知相同的通知合并,只留下當前加入的
除了在添加通知時指定通知的合并策略,也可以調用dequeueNotificationsMatching: coalesceMask:方法對queue中存在的通知進行清除。
調用enqueueNotification:方法時,如果指定了合并策略,底層其實也是在將通知添加進queue前先調用了一次dequeueNotificationsMatching: coalesceMask:方法。
[queue enqueueNotification:noti4 postingStyle:NSPostNow coalesceMask:NSNotificationCoalescingOnName forModes:nil];
===== 這句與下面這兩句代碼 作用是一樣的 =====
[queue dequeueNotificationsMatching:noti4 coalesceMask:NSNotificationCoalescingOnName];
[queue enqueueNotification:noti4 postingStyle:NSPostNow coalesceMask:NSNotificationNoCoalescing forModes:nil];
將前面的代碼修改一下,只在結尾處添加一個dequeueNotificationsMatching方法:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSNotificationQueue *queue = [[NSNotificationQueue alloc] initWithNotificationCenter:[NSNotificationCenter defaultCenter]];
NSNotification *noti1 = [[NSNotification alloc] initWithName:@"noti_name" object:nil userInfo:@{@"noti":@"1"}];
NSNotification *noti2 = [[NSNotification alloc] initWithName:@"noti_name" object:@"123" userInfo:@{@"noti":@"2"}];
NSNotification *noti3 = [[NSNotification alloc] initWithName:@"noti_name" object:@"123" userInfo:@{@"noti":@"3"}];
NSNotification *noti4 = [[NSNotification alloc] initWithName:@"noti_name" object:nil userInfo:@{@"noti":@"4"}];
NSLog(@"enqueue noti1");
[queue enqueueNotification:noti1 postingStyle:NSPostWhenIdle coalesceMask:NSNotificationNoCoalescing forModes:nil];
NSLog(@"enqueue noti2");
[queue enqueueNotification:noti2 postingStyle:NSPostASAP coalesceMask:NSNotificationNoCoalescing forModes:nil];
NSLog(@"enqueue noti3");
[queue enqueueNotification:noti3 postingStyle:NSPostNow coalesceMask:NSNotificationNoCoalescing forModes:@[@"123"]];
NSLog(@"enqueue noti4");
[queue enqueueNotification:noti4 postingStyle:NSPostNow coalesceMask:NSNotificationNoCoalescing forModes:@[NSRunLoopCommonModes]];
[queue dequeueNotificationsMatching:noti2 coalesceMask:NSNotificationCoalescingOnName];
}
2018-11-23 14:35:01.444270+0800 Nunca[57093:8581368] enqueue noti1
2018-11-23 14:35:01.444611+0800 Nunca[57093:8581368] enqueue noti2
2018-11-23 14:35:01.445167+0800 Nunca[57093:8581368] enqueue noti3
2018-11-23 14:35:01.446550+0800 Nunca[57093:8581368] noti==NSConcreteNotification 0x600000241a70 {name = noti_name; object = 123; userInfo = {
noti = 3;
}}
2018-11-23 14:35:01.446687+0800 Nunca[57093:8581368] enqueue noti4
2018-11-23 14:35:01.446859+0800 Nunca[57093:8581368] noti==NSConcreteNotification 0x600000241a40 {name = noti_name; userInfo = {
noti = 4;
}}
在執行dequeueNotificationsMatching方法時,queue中存在noti1、noti2,由于noti1、noti2與用來匹配的noti2的name一致,因此都被dequeue了。
modes
modes可以指定通知在runloop的哪幾種mode中執行,但是對于postingStyle為NSPostNow的,即使runloop不是運行在指定mode,通知也會在當前mode立刻被發送。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSNotificationQueue *queue = [[NSNotificationQueue alloc] initWithNotificationCenter:[NSNotificationCenter defaultCenter]];
NSNotification *noti1 = [[NSNotification alloc] initWithName:@"noti_name" object:nil userInfo:@{@"noti":@"1"}];
NSNotification *noti2 = [[NSNotification alloc] initWithName:@"noti_name" object:@"123" userInfo:@{@"noti":@"2"}];
NSNotification *noti3 = [[NSNotification alloc] initWithName:@"noti_name" object:@"123" userInfo:@{@"noti":@"3"}];
NSNotification *noti4 = [[NSNotification alloc] initWithName:@"noti_name" object:nil userInfo:@{@"noti":@"4"}];
NSLog(@"enqueue noti1");
[queue enqueueNotification:noti1 postingStyle:NSPostWhenIdle coalesceMask:NSNotificationNoCoalescing forModes:@[@"123"]];
NSLog(@"enqueue noti2");
[queue enqueueNotification:noti2 postingStyle:NSPostASAP coalesceMask:NSNotificationNoCoalescing forModes:@[NSDefaultRunLoopMode]];
NSLog(@"enqueue noti3");
[queue enqueueNotification:noti3 postingStyle:NSPostNow coalesceMask:NSNotificationNoCoalescing forModes:@[@"123"]];
NSLog(@"enqueue noti4");
[queue enqueueNotification:noti4 postingStyle:NSPostNow coalesceMask:NSNotificationNoCoalescing forModes:@[NSRunLoopCommonModes]];
}
2018-11-23 14:40:49.655868+0800 Nunca[57168:8589299] enqueue noti1
2018-11-23 14:40:49.656529+0800 Nunca[57168:8589299] enqueue noti2
2018-11-23 14:40:49.657081+0800 Nunca[57168:8589299] enqueue noti3
2018-11-23 14:40:49.658542+0800 Nunca[57168:8589299] noti==NSConcreteNotification 0x600000447cb0 {name = noti_name; object = 123; userInfo = {
noti = 3;
}}
2018-11-23 14:40:49.658682+0800 Nunca[57168:8589299] enqueue noti4
2018-11-23 14:40:49.658838+0800 Nunca[57168:8589299] noti==NSConcreteNotification 0x6000004477d0 {name = noti_name; userInfo = {
noti = 4;
}}
2018-11-23 14:40:49.659112+0800 Nunca[57168:8589299] noti==NSConcreteNotification 0x600000447c20 {name = noti_name; object = 123; userInfo = {
noti = 2;
}}
2.4接收通知后監聽方法執行的線程
對于利用NSNotificationQueue發送通知的方式,監聽方法執行的線程與通知被加入至隊列時所在的線程一致。
四、總結
使用通知時的注意點:
1.使用 addObserver: selector: 添加監聽者時,監聽方法執行時所在線程與發送通知所在的線程一致,若是在監聽的方法中添加處理UI或其他需要指定線程處理的任務,則需要注意。
2.postNotif 方式發送通知時,會同步去處理監聽者的回掉,如果回掉中有耗時任務,則容易阻塞當前線程。
3.iOS9以后不移除observer也不會導致程序崩潰,但最好當然是要用完就移除啦。
4........(想到再補充)