NSNotificationCenter的使用指南及自實現

什麼是NSNotificationCenter

NSNotificationCenter是一種通知調度機制,容許向註冊的觀察者廣播信息。php

一個消息能夠被多個觀察者接收,一個觀察者也能夠接收多個消息。git

NSNotificationCenter只能在單個應用程序中傳遞通知;若是要將通知發佈到其餘進程或接收來自其餘進程的通知,請使用NSDistributedNotificationCenter(注意只在macOS 10.0+可使用,iOS中不可以使用)。github

NSNotificationCenter的使用詳解

用前須知

通知三要素:通知名稱(name,必填)、發送者(object,可選)和內容(userInfo,可選)。這個就像是發郵件同樣,必定要指定郵件的主題(對應通知名稱),不然是不讓發送的。發送者和內容能夠不指定。swift

API中也指明瞭name屬性不能爲nil。bash

API的使用

添加觀察者,訂閱通知

  • -(void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject; --(target方式)數據結構

    當name爲空時,表示任意名稱的通知都訂閱。app

    當object爲空時,表示任意發送者都通知都訂閱。less

    當name和object都爲空時,表示訂閱全部通知,含系統通知,能夠經過此方法監聽到全部系統通知。post

    當name和object都不爲空時,表示只訂閱指定通知名稱和發送者都通知。測試

  • -(id )addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block --(block方式)

發送通知

  • -(void)postNotification:(NSNotification *)notification;

    建立NSNotification的正確姿式:

    • +(instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject;--(注意:name爲必填)
    • +(instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;--(注意:name爲必填)
    • -(instancetype)initWithName:(NSNotificationName)name object:(nullable id)object userInfo:(nullable NSDictionary *)userInfo; --(注意:name爲必填)
  • -(void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject; --(注意:name爲必填)

  • -(void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;--(注意:name爲必填)

刪除觀察者,取消訂閱

  • -(void)removeObserver:(id)observer;

    observer必填字段,表示取消當前觀察者對象下的全部訂閱。

  • -(void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;

    observer必填字段,表示當前觀察者對象。

    name爲空,表示任何名字的通知都取消訂閱。

    object爲空,表示任意發送者的通知都取消訂閱。

    name和object都爲空,等同於-(void)removeObserver:(id)observer,表示取消當前觀察者對象中的全部訂閱。

    name和object都不爲空,表示指定名稱和發送者的通知都取消訂閱。

NSNotificationCenter的注意事項

使用target方式添加的observer是否須要移除?

官方文檔說明:

If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its dealloc method. Otherwise, you should call removeObserver:name:object: before observer or any object passed to this method is deallocated.

就是說iOS 9.0之後,不須要在dealloc method中移除observer了,不然須要在dealloc中調用removeObserver:name:object:方法移除。

使用block方式添加的observer的注意事項

block內不能強引用調用self,不然會產生內存泄漏

由於NSNotificationCenter持有block,而block持有observer(即當前ViewController),因此當前ViewController不能被釋放。

須要手動移除,不然block一直在NSNotificationCenter中

官方文檔說明:

The block is copied by the notification center and (the copy) held until the observer registration is removed.

也就是說,若是不手動移除的話,block就會一直存在。

驗證方式:

操做步驟:
一、ViewControllerA  push ->  ViewControllerB
二、ViewControllerB pop -> ViewControllerA
三、ViewControllerA發送通知

ViewControllerB viewDidLoad方法中add observer:

self.testObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"TestName" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
   NSLog(@"note = %@", note);
}];

ViewControllerB dealloc方法中加入Log,觀察ViewControllerB是否正常釋放:
NSLog(@"%s", __func__);

ViewControllerA 點擊按鈕發送通知:
[[NSNotificationCenter defaultCenter] postNotificationName:@"TestName" object:nil];

測試結果是:ViewControllerB釋放後,還能打印note,表示block沒有被釋放。

複製代碼

正確姿式:

__weak typeof(self) weakSelf = self;
self.testObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"TestName" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
    
    __strong typeof(weakSelf) self = weakSelf;
    if (!self) return;
    
    //TODO 寫具體邏輯
    
}];

-(void)dealloc {
    //必定要釋放
    [[NSNotificationCenter defaultCenter] removeObserver:self.testObserver];
}
複製代碼

閱讀源碼

gnustep源碼

www.gnustep.org/resources/d… 中下載GNUstep Base 1.26.0。 代碼比較簡單,配合如下核心數據結構圖自行閱讀便可。

nameless:哈希表,鍵爲object,值爲觀察者集合(鏈表存儲)。存儲的是全部只有object的觀察者信息。

name:哈希表,鍵爲name,值爲object的哈希表。存儲name不能空的全部觀察者信息。

wildcard:觀察者集合。存儲name和object都爲空的觀察者信息。

蘋果源碼

github.com/apple/swift…

代碼比較簡單,自行閱讀便可。

本身動手實現(參考蘋果開源實現)

JBNotificationCenter.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

typedef NSString* JBNotificationName;

@interface JBNotification : NSObject

+ (instancetype)notificationWithName:(JBNotificationName)aName
                              object:(nullable id)anObject;

+ (instancetype)notificationWithName:(JBNotificationName)aName
                              object:(nullable id)anObject
                            userInfo:(nullable NSDictionary *)aUserInfo;


@end



@interface JBNotificationCenter : NSObject 

@property (class, readonly, strong) JBNotificationCenter *defaultCenter;

/**
 添加觀察者

 @param observer 觀察者
 @param aSelector 回調方法
 @param aName 通知名稱
 @param anObject 通知關聯對象
 */
- (void)addObserver:(id)observer
           selector:(SEL)aSelector
               name:(nullable JBNotificationName)aName
             object:(nullable id)anObject;


/**
 發送通知

 @param notification 通知對象
 */
- (void)postNotification:(JBNotification *)notification;


/**
 發送通知

 @param aName 通知名稱
 @param anObject 通知關聯對象
 */
- (void)postNotificationName:(JBNotificationName)aName
                      object:(nullable id)anObject;


/**
 發送通知

 @param aName 通知名稱
 @param anObject 通知關聯對象
 @param aUserInfo 參數
 */
- (void)postNotificationName:(JBNotificationName)aName
                      object:(nullable id)anObject
                    userInfo:(nullable NSDictionary *)aUserInfo;


/**
 刪除觀察者

 @param observer 觀察者
 */
- (void)removeObserver:(id)observer;


/**
 刪除觀察者

 @param observer 觀察者
 @param aName 通知名稱
 @param anObject 通知關聯對象
 */
- (void)removeObserver:(id)observer
                  name:(nullable JBNotificationName)aName
                object:(nullable id)anObject;


/**
 添加觀察者

 @param name 通知名稱
 @param obj 通知關聯對象
 @param queue 通知所屬隊列
 @param block 回調
 @return 擔任操做觀察者的隱藏對象
 */
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(JBNotification *note))block;


@end

NS_ASSUME_NONNULL_END
複製代碼

JBNotificationCenter.m

#import "JBNotificationCenter.h"

@interface JBNotificationReceiver : NSObject

@property(nonatomic, weak) id observer; //觀察者

@property(nonatomic, assign) SEL selector;  //觀察者回調選擇器

@property(nonatomic, copy) NSString *name;//通知名稱

@property(nonatomic, weak) id sender;//發送者

@end

@implementation JBNotificationReceiver

@end




@interface JBNotification ()

@property(nonatomic, copy) NSString *name;

@property(nonatomic, strong) id object;

@property(nonatomic, strong) NSDictionary *userInfo;

@end
@implementation JBNotification

+ (instancetype)notificationWithName:(JBNotificationName)aName
                              object:(nullable id)anObject {
    
    return [self notificationWithName:aName object:anObject userInfo:nil];
}





+ (instancetype)notificationWithName:(JBNotificationName)aName
                              object:(nullable id)anObject
                            userInfo:(nullable NSDictionary *)aUserInfo {
    
    JBNotification *notification = [[JBNotification alloc] init];
    notification.name = aName;
    notification.object = anObject;
    notification.userInfo = aUserInfo;
    
    return notification;
}

- (NSString *)description {
    return [NSString stringWithFormat:@"%@ %p {name = %@; object = %@}",self.class,self, self.name, self.object];
}

@end

typedef void (^JBNotificationBlock)(JBNotification *note);

@interface JBBlockNotificationObserver : NSObject

@property(nonatomic, weak) NSOperationQueue *queue;

@property(nonatomic, copy) JBNotificationBlock block;


- (id) initWithQueue: (NSOperationQueue *)queue block: (JBNotificationBlock)block;


@end

@implementation JBBlockNotificationObserver


- (id) initWithQueue: (NSOperationQueue *)queue block: (JBNotificationBlock)block {
    self = [super init];
    if (self) {
        self.queue = queue;
        self.block = block;
    }
    return self;
}

- (void) didReceiveNotification: (JBNotification *)note {
    
    if (self.queue && self.queue != NSOperationQueue.currentQueue) {
        
        [self.queue addOperationWithBlock:^{
            self.block(note);
        }];
        
        [self.queue waitUntilAllOperationsAreFinished];
        
    } else {
        self.block(note);
    }
    
    
}

@end


@interface JBNotificationCenter ()

@property(nonatomic, strong) NSMutableArray<JBNotificationReceiver *> *observers;

@property(nonatomic, strong) NSLock *observerLock;



@end
@implementation JBNotificationCenter

+ (instancetype)defaultCenter {
    
    static id instance = nil;
    
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    
    return instance;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        self.observers = [[NSMutableArray alloc] initWithCapacity:0];
        self.observerLock = [[NSLock alloc] init];
    }
    return self;
}


/**
 添加觀察者
 
 @param observer 觀察者
 @param aSelector 回調方法
 @param name 通知名稱
 @param object 通知關聯對象
 */
- (void)addObserver:(id)observer
           selector:(SEL)aSelector
               name:(nullable JBNotificationName)name
             object:(nullable id)object {
    
    //參數的校驗
    if (observer == nil)
        [NSException raise: NSInvalidArgumentException
                    format: @"Nil observer passed to addObserver ..."];
    
    if (aSelector == 0)
        [NSException raise: NSInvalidArgumentException
                    format: @"Null selector passed to addObserver ..."];
    
    if ([observer respondsToSelector: aSelector] == NO){
        [NSException raise: NSInvalidArgumentException
                    format: @"[%@-%@] Observer '%@' does not respond to selector '%@'",
         NSStringFromClass([self class]), NSStringFromSelector(_cmd),
         observer, NSStringFromSelector(aSelector)];
    }
    
    
    [self.observerLock lock];
    
    JBNotificationReceiver *notificationReceiver = [[JBNotificationReceiver alloc] init];
    
    notificationReceiver.observer = observer;
    notificationReceiver.selector = aSelector;
    notificationReceiver.name = name;
    notificationReceiver.sender = object;
    
    [self.observers addObject:notificationReceiver];
    
    
    [self.observerLock unlock];
    
    
}

/**
 添加觀察者
 
 @param name 通知名稱
 @param object 通知關聯對象
 @param queue 通知所屬隊列
 @param block 回調
 @return 擔任操做觀察者的隱藏對象
 */
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)object queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(JBNotification *note))block {
    
    [self.observerLock lock];
    
    JBNotificationReceiver *notificationReceiver = [[JBNotificationReceiver alloc] init];
    
    JBBlockNotificationObserver *observer = [[JBBlockNotificationObserver alloc] initWithQueue:queue block:block];

    notificationReceiver.observer = observer;
    notificationReceiver.selector = @selector(didReceiveNotification:);
    notificationReceiver.name = name;
    notificationReceiver.sender = object;
    
    [self.observers addObject:notificationReceiver];
    
    [self.observerLock unlock];
    
    return observer;
}

/**
 發送通知
 
 @param name 通知名稱
 @param object 通知關聯對象
 */
- (void)postNotificationName:(JBNotificationName)name object:(nullable id)object {
    [self postNotificationName:name object:object userInfo:nil];
}


/**
 發送通知
 
 @param name 通知名稱
 @param object 通知關聯對象
 @param userInfo 參數
 */
- (void)postNotificationName:(JBNotificationName)name
                      object:(nullable id)object
                    userInfo:(nullable NSDictionary *)userInfo {
    
    JBNotification *notification = [JBNotification notificationWithName:name object:object userInfo:userInfo];
    
    [self postNotification:notification];
}

/**
 發送通知
 
 @param notification 通知對象
 */
- (void)postNotification:(JBNotification *)notification {
    
    //查找符合條件的observer
    
    [self.observerLock lock];

    NSMutableArray<JBNotificationReceiver *> *sendTo = [[NSMutableArray alloc] init];
    
    for (JBNotificationReceiver *receiver in self.observers) {
        
        if ((receiver.name == nil || [receiver.name isEqualToString:notification.name])
            &&(receiver.sender == nil || [receiver.sender isEqual:notification.object])) {
            
            [sendTo addObject:receiver];
        }
        
    }
    [self.observerLock unlock];
    
    
    NSMutableArray<JBNotificationReceiver *> *delete = [[NSMutableArray alloc] init];
    
    for (JBNotificationReceiver *receiver in sendTo) {
        if (receiver.observer) {
            #pragma clang diagnostic push
            #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
                         [receiver.observer performSelector:receiver.selector withObject:notification];
            #pragma clang diagnostic pop
        } else {
            [delete addObject:receiver];
        }
    }
    
    [self.observerLock lock];
    
    [self.observers removeObjectsInArray:delete];
    
    [self.observerLock unlock];
    
}


/**
 刪除觀察者
 
 @param observer 觀察者
 */
- (void)removeObserver:(id)observer {
    
    [self removeObserver:observer name:nil object:nil];
}


/**
 刪除觀察者
 
 @param observer 觀察者
 @param name 通知名稱
 @param object 通知關聯對象
 */
- (void)removeObserver:(id)observer
                  name:(nullable JBNotificationName)name
                object:(nullable id)object {
    
    [self.observerLock lock];
    
    NSMutableArray<JBNotificationReceiver *> *keep = [[NSMutableArray alloc] init];
    
    for (JBNotificationReceiver *receiver in self.observers) {
        
        //通知對象不同,留下
        if (receiver.observer != nil && receiver.observer != observer) {
            [keep addObject:receiver];
            continue;
        }
        
        //通知對象同樣的狀況下,當前名稱不爲空,且名稱不同,留下
        if (name != nil && ![name isEqualToString:receiver.name]) {
            [keep addObject:receiver];
            continue;
        }
        
        //通知名稱同樣的狀況下,當前sender不爲空,且發送者不同,留下
        if (object != nil && receiver.observer != object) {
            [keep addObject:receiver];
        }
    }
    
    self.observers = keep;
    
    [self.observerLock unlock];
}
@end
複製代碼
相關文章
相關標籤/搜索