NSNotification學(xué)習(xí)筆記

一、添加通知監(jiān)聽者的方式

- (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 ;

以上是添加通知監(jiān)聽者的兩種方式

1. 添加監(jiān)聽者時的name與object

兩種方式添加監(jiān)聽者傳入的參數(shù)都包含name和object醋火,這兩個參數(shù)都可以為空昧廷,關(guān)于這兩個參數(shù)主要有以下四種情況:

1.1 若是name和object同時為空則可以監(jiān)聽程序中所有的通知

- (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"}];
}

添加了兩個監(jiān)聽者被碗,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;
}}

監(jiān)聽到了程序中所有發(fā)出來的通知奏属,上面截取了一部分伶丐,最后打印的是點擊事件中自己添加的悼做。

1.2 若是name不為空、object為空則可以監(jiān)聽所有發(fā)送時通知名稱與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"}];
}

添加兩個監(jiān)聽者哗魂,監(jiān)聽的name相同贿堰,object都為空,在點擊屏幕時發(fā)送兩條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";
}}

監(jiān)聽者name不為空、object為空時庶灿,不管發(fā)送通知時有沒有傳入object纵搁,只要name對上了,就能被成功監(jiān)聽往踢。

1.3 若是name為空腾誉、object不為空則可以監(jiān)聽所有發(fā)送通知時傳入的object與監(jiān)聽傳入的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為空的監(jiān)聽者,object一個為當前控制器峻呕,一個為字符串123
發(fā)送通知時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";
}}

監(jiān)聽者name為空利职、object不為空時,只要發(fā)送通知時傳入的object是與監(jiān)聽者的相同瘦癌、不管發(fā)送通知的name是什么通知都能被成功監(jiān)聽猪贪。

1.4 若name與object都不為空,則只有發(fā)送時通知名稱與name相同并且傳入的object與監(jiān)聽傳入的object為同一個對象的通知能被成功監(jiān)聽

- (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"}];
}

若監(jiān)聽者的name與object都不為空讯私,那只有發(fā)送通知時傳入的name與object都與監(jiān)聽者的相同通知才能被成功監(jiān)聽热押。

關(guān)于監(jiān)聽者的name與object.png
2 通知發(fā)送與監(jiān)聽的線程

回到以上兩種添加監(jiān)聽者的方法,上面列出來的是它們的一些共性斤寇,那他們的差異也是存在的桶癣。

2.1 queue為nil的時候
對于第一種傳入selector方式,通知在哪個線程發(fā)送則在哪個線程被同步回掉娘锁。
對于第二種傳入block形式的牙寞,可以傳入一個NSOperationQueue,指定block被加入至哪個隊列莫秆,若穿入的是空间雀,則block會被在通知發(fā)送時所在線程同步調(diào)用(與第一種方式相同了)悔详。

- (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];
}

在主線程添加了通知監(jiān)聽者(以第二種方式添加的監(jiān)聽者傳入的queue為nil),然后在子線程發(fā)送通知雷蹂。
另外可以看到第一種方式添加監(jiān)聽者時伟端,監(jiān)聽者是我們自己傳入指定的,第二種方式則是創(chuàng)建并返回了一個監(jiān)聽者匪煌,因此第二種需要我們額外保存返回的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

可以看到萎庭,監(jiān)聽者接收到通知執(zhí)行監(jiān)聽方法時所在的線程與通知發(fā)送所在線程一致霜医,與添加監(jiān)聽者時所在的線程無關(guān)。另外驳规,在通知發(fā)送后先去執(zhí)行了監(jiān)聽者的方法之后再繼續(xù)執(zhí)行當前代碼(打印--finish)肴敛,說明通知的發(fā)送與監(jiān)聽是同步執(zhí)行的。

2.2 指定queue的時候
對于第二種添加監(jiān)聽者的方式吗购,也可以指定執(zhí)行block所在queue医男,當監(jiān)聽到通知后要更新UI的時候就適合以這種方式添加監(jiān)聽者,此時將queue設(shè)置為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

可以看到通知雖然在子線程被發(fā)送镀梭,但監(jiān)聽方法是在主線程執(zhí)行,并且也是同步的踱启,在監(jiān)聽方法中的所有任務(wù)執(zhí)行完成之后才會繼續(xù)原來的程序报账,因此也需要注意,發(fā)送通知時埠偿,很可能因為監(jiān)聽者需要處理耗時任務(wù)而使得通知發(fā)送所在的線程被阻塞透罢。

二、存儲observer的結(jié)構(gòu)

首先根據(jù)GNUstep的代碼冠蒋,找出幾個關(guān)鍵結(jié)構(gòu)

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;

主要關(guān)注前三個:
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監(jiān)聽所有通知。

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,構(gòu)成一個MapNode結(jié)構(gòu)腕扶,再以鏈表形式保存在nameless孽拷。


nameless.png

3.GSIMapTable named;
當observer的name不為空時,observer被存入named半抱。
以observer的name為key脓恕,value為一個集合(or鏈表結(jié)構(gòu))膜宋,集合中存儲的結(jié)構(gòu)是以object為key、以對應(yīng)的observers為value炼幔。


named.png
添加observer時的具體步驟:

1.判斷observer的name是否為空秋茫,如果不為空則進入named表,再以observer的name為key去named表中索引對應(yīng)的下一層級的表乃秀,如果索引出來不存在則創(chuàng)建肛著,取得下一層級的表的時候,再以observer的object為key去索引跺讯,object即使為空也可以索引到對應(yīng)的集合(或者鏈表)枢贿,將observer存入。
2.如果observer的name為空刀脏,則繼續(xù)判斷object是否為空局荚,若object不為空,則以object為key去nameless表中查找對應(yīng)的集合愈污,并將observer存入
3.如果object也為空耀态,則將observer添加至wildcard

移除observer時的具體步驟:

1.如果name與object都為空
1.1 先去wildcard去查找、刪除
1.2 再繼續(xù)步驟2與3
2.如果name為空
.......(有空再補充)
3.如果name不為空
.......(有空再補充)

三暂雹、發(fā)送通知的方式

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;

這是最常見的發(fā)送通知的方式首装,上面的例子也全部基于此種通知發(fā)送的方式。這種方式發(fā)送的通知都是同步地去執(zhí)行監(jiān)聽方法擎析。

2.NSNotificationQueue
2.1NSNotificationQueue的結(jié)構(gòu)
@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對象簿盅,即使多次調(diào)用initWithNotificationCenter,實際上也都是返回的同一個揍魂,只有切換線程桨醋,獲取的才會是一個不一樣的。
(這點與GNUstep的源碼是有出路的现斋,按照GNUstep中的實現(xiàn)每次initWithNotificationCenter應(yīng)該都會生成一個新的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>

每個線程存在并指存在一個與之對應(yīng)的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決定通知發(fā)送的時機虫蝶,有三種:
NSPostWhenIdle:空閑的時候發(fā)送
NSPostASAP:盡快發(fā)送,一般在runloop結(jié)束當前l(fā)oop之后
NSPostNow :立刻發(fā)送倦西,添加通知進隊列地同步發(fā)送 (如果指定了合并策略會先進行通知合并)

- (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 :將加入新通知之前已經(jīng)存在queue中的name與新通知相同的通知合并(清除)
NSNotificationCoalescingOnSender :將queue中object與當前通知相同的通知合并,只留下當前加入的

除了在添加通知時指定通知的合并策略,也可以調(diào)用dequeueNotificationsMatching: coalesceMask:方法對queue中存在的通知進行清除粉铐。

調(diào)用enqueueNotification:方法時疼约,如果指定了合并策略,底層其實也是在將通知添加進queue前先調(diào)用了一次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];

將前面的代碼修改一下程剥,只在結(jié)尾處添加一個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;
}}

在執(zhí)行dequeueNotificationsMatching方法時,queue中存在noti1汤踏、noti2织鲸,由于noti1、noti2與用來匹配的noti2的name一致茎活,因此都被dequeue了昙沦。

modes

modes可以指定通知在runloop的哪幾種mode中執(zhí)行,但是對于postingStyle為NSPostNow的载荔,即使runloop不是運行在指定mode盾饮,通知也會在當前mode立刻被發(fā)送。

- (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接收通知后監(jiān)聽方法執(zhí)行的線程

對于利用NSNotificationQueue發(fā)送通知的方式懒熙,監(jiān)聽方法執(zhí)行的線程與通知被加入至隊列時所在的線程一致丘损。

四、總結(jié)

使用通知時的注意點:
1.使用 addObserver: selector: 添加監(jiān)聽者時工扎,監(jiān)聽方法執(zhí)行時所在線程與發(fā)送通知所在的線程一致徘钥,若是在監(jiān)聽的方法中添加處理UI或其他需要指定線程處理的任務(wù),則需要注意肢娘。
2.postNotif 方式發(fā)送通知時呈础,會同步去處理監(jiān)聽者的回掉,如果回掉中有耗時任務(wù)橱健,則容易阻塞當前線程而钞。
3.iOS9以后不移除observer也不會導(dǎo)致程序崩潰,但最好當然是要用完就移除啦拘荡。
4........(想到再補充)

主要參考:

深入理解iOS NSNotification
淺談 iOS NSNotification

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末臼节,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子珊皿,更是在濱河造成了極大的恐慌网缝,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蟋定,死亡現(xiàn)場離奇詭異粉臊,居然都是意外死亡,警方通過查閱死者的電腦和手機驶兜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門维费,熙熙樓的掌柜王于貴愁眉苦臉地迎上來果元,“玉大人促王,你說我怎么就攤上這事犀盟。” “怎么了蝇狼?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵阅畴,是天一觀的道長。 經(jīng)常有香客問我迅耘,道長贱枣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任颤专,我火速辦了婚禮纽哥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘栖秕。我一直安慰自己春塌,他們只是感情好,可當我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布簇捍。 她就那樣靜靜地躺著只壳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪暑塑。 梳的紋絲不亂的頭發(fā)上吼句,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天,我揣著相機與錄音事格,去河邊找鬼惕艳。 笑死,一個胖子當著我的面吹牛驹愚,可吹牛的內(nèi)容都是我干的远搪。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼么鹤,長吁一口氣:“原來是場噩夢啊……” “哼终娃!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蒸甜,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤棠耕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后柠新,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體窍荧,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年恨憎,在試婚紗的時候發(fā)現(xiàn)自己被綠了蕊退。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片郊楣。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖瓤荔,靈堂內(nèi)的尸體忽然破棺而出净蚤,到底是詐尸還是另有隱情,我是刑警寧澤输硝,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布今瀑,位于F島的核電站,受9級特大地震影響点把,放射性物質(zhì)發(fā)生泄漏橘荠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一郎逃、第九天 我趴在偏房一處隱蔽的房頂上張望哥童。 院中可真熱鬧,春花似錦褒翰、人聲如沸贮懈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽错邦。三九已至,卻和暖如春型宙,著一層夾襖步出監(jiān)牢的瞬間撬呢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工妆兑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留魂拦,地道東北人。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓搁嗓,卻偏偏與公主長得像芯勘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子腺逛,可洞房花燭夜當晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 29,387評論 8 265
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理荷愕,服務(wù)發(fā)現(xiàn),斷路器棍矛,智...
    卡卡羅2017閱讀 134,657評論 18 139
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,101評論 1 32
  • http://liuxing.info/2017/06/30/Spring%20AMQP%E4%B8%AD%E6%...
    sherlock_6981閱讀 15,914評論 2 11
  • 小貓想用自以為鋒利的爪子爬上樹 嬰兒想用自以為強健的手掌攀上滑梯 長大的樹鋪開陰影對抗陽光 不想長大的你留了長發(fā)對...
    見素抱樸_MPES閱讀 325評論 0 2