NSNotificationCenter

在 Objective-C 中,NSNotificationCenter(通知中心) 是基于觀察者模式實(shí)現(xiàn)的跨對(duì)象通信機(jī)制,用于解耦不同組件間的消息傳遞。要理解它的內(nèi)部工作原理,以及自己如何實(shí)現(xiàn)一個(gè)簡(jiǎn)易版通知中心,我們可以從底層原理自定義實(shí)現(xiàn)兩方面展開分析。

一、OC 原生通知中心(NSNotificationCenter)的內(nèi)部工作原理

NSNotificationCenter 的核心是管理觀察者的注冊(cè)、通知的發(fā)送與分發(fā),其內(nèi)部主要分為主線程通知中心全局通知中心+defaultCenter),核心邏輯包括以下幾個(gè)關(guān)鍵環(huán)節(jié):

1. 觀察者注冊(cè)(addObserver 相關(guān)方法)

當(dāng)調(diào)用 addObserver:selector:name:object:addObserverForName:object:queue:usingBlock: 時(shí),通知中心會(huì)做這些事:

  • 存儲(chǔ)觀察者信息:內(nèi)部維護(hù)一個(gè)哈希表/字典,以通知名稱(name)發(fā)送者(object) 為鍵,存儲(chǔ)對(duì)應(yīng)的觀察者列表(包含觀察者對(duì)象、響應(yīng)方法/Block、隊(duì)列等信息)。
  • 處理重復(fù)注冊(cè):默認(rèn)允許同一觀察者對(duì)同一通知重復(fù)注冊(cè)(會(huì)多次響應(yīng)),需開發(fā)者自行保證唯一性。
  • 弱引用觀察者:為了避免循環(huán)引用,通知中心對(duì)觀察者對(duì)象持弱引用(ARC 下),如果觀察者被釋放,通知中心不會(huì)保留它(但舊版本手動(dòng)管理內(nèi)存時(shí)需手動(dòng)移除觀察者)。

2. 發(fā)送通知(postNotification 相關(guān)方法)

當(dāng)調(diào)用 postNotification:postNotificationName:object:userInfo: 時(shí),通知中心會(huì)執(zhí)行:

  • 匹配觀察者:根據(jù)通知名稱(name)和發(fā)送者(object),從哈希表中查找對(duì)應(yīng)的所有觀察者。
  • 分發(fā)通知
    • 如果是同步發(fā)送:直接在當(dāng)前線程調(diào)用觀察者的響應(yīng)方法/Block。
    • 如果是異步發(fā)送(通過 addObserverForName:object:queue:usingBlock: 指定隊(duì)列):將通知分發(fā)任務(wù)提交到指定的 GCD 隊(duì)列中執(zhí)行。
  • 處理 nil 匹配:如果通知名稱為 nil,會(huì)匹配所有名稱的通知;如果發(fā)送者為 nil,會(huì)匹配所有發(fā)送者的通知。

3. 移除觀察者(removeObserver 相關(guān)方法)

當(dāng)調(diào)用 removeObserver:removeObserver:name:object: 時(shí),通知中心會(huì):

  • 清理觀察者列表:從哈希表中移除指定觀察者的相關(guān)注冊(cè)信息,避免野指針調(diào)用。
  • 自動(dòng)移除(ARC 優(yōu)化):在 iOS 9 / macOS 10.11 之后,通知中心會(huì)自動(dòng)對(duì)已釋放的觀察者做清理,無需手動(dòng)移除(但建議顯式移除以規(guī)范)。

4. 特殊的通知隊(duì)列(NSNotificationQueue)

NSNotificationQueue 是通知的緩沖隊(duì)列,基于 RunLoop 實(shí)現(xiàn),用于批量處理通知:

  • 可設(shè)置合并策略(如同一通知合并為一次)。
  • 延遲發(fā)送通知,避免短時(shí)間內(nèi)大量通知導(dǎo)致的性能問題。

5. 線程特性

  • 通知的分發(fā)線程發(fā)送線程決定(同步發(fā)送時(shí)),如果需要在主線程處理,需在觀察者的響應(yīng)方法中手動(dòng)切線程。
  • Block 方式注冊(cè)時(shí)可指定隊(duì)列,通知會(huì)在指定隊(duì)列中執(zhí)行,無需手動(dòng)切線程。

二、自定義實(shí)現(xiàn)一個(gè)簡(jiǎn)易的 OC 通知中心

如果要自己實(shí)現(xiàn)一個(gè)通知中心,核心需要實(shí)現(xiàn)觀察者注冊(cè)、通知發(fā)送、觀察者移除三大功能,同時(shí)要解決弱引用、線程安全、匹配規(guī)則等問題。以下是分步實(shí)現(xiàn)思路和代碼示例:

設(shè)計(jì)思路

  1. 數(shù)據(jù)結(jié)構(gòu):用字典存儲(chǔ)觀察者信息,鍵為通知名稱+發(fā)送者的組合,值為觀察者數(shù)組。
  2. 觀察者模型:封裝觀察者的對(duì)象、響應(yīng)方法/Block、隊(duì)列等信息,避免直接存儲(chǔ)強(qiáng)引用。
  3. 線程安全:使用鎖(NSLock/dispatch_semaphore_t)保證多線程下的讀寫安全。
  4. 弱引用處理:對(duì)觀察者對(duì)象使用弱引用,避免循環(huán)引用。
  5. 通知分發(fā):支持同步分發(fā),可選異步分發(fā)(基于 GCD 隊(duì)列)。

代碼實(shí)現(xiàn)

1. 定義觀察者模型(HYBObserver.h/m)

用于封裝觀察者的相關(guān)信息(弱引用觀察者、響應(yīng)方法、Block、隊(duì)列等)。

// HYBObserver.h
#import <Foundation/Foundation.h>

@class HYBNotification;

typedef void (^HYBNotificationBlock)(HYBNotification *notification);

@interface HYBObserver : NSObject
/// 弱引用觀察者對(duì)象
@property (nonatomic, weak) id observer;
/// 響應(yīng)方法(SEL)
@property (nonatomic, assign) SEL selector;
/// 響應(yīng) Block
@property (nonatomic, copy) HYBNotificationBlock block;
/// 執(zhí)行隊(duì)列(Block 方式時(shí)使用)
@property (nonatomic, strong) dispatch_queue_t queue;
/// 通知名稱(用于快速匹配)
@property (nonatomic, copy) NSString *name;
/// 發(fā)送者(用于快速匹配)
@property (nonatomic, weak) id object;

/// 初始化 SEL 類型的觀察者
+ (instancetype)observerWithObserver:(id)observer 
                            selector:(SEL)selector 
                                name:(NSString *)name 
                              object:(id)object;

/// 初始化 Block 類型的觀察者
+ (instancetype)observerWithObserver:(id)observer 
                               block:(HYBNotificationBlock)block 
                               queue:(dispatch_queue_t)queue 
                                 name:(NSString *)name 
                               object:(id)object;
@end

// HYBObserver.m
#import "HYBObserver.h"

@implementation HYBObserver
+ (instancetype)observerWithObserver:(id)observer 
                            selector:(SEL)selector 
                                name:(NSString *)name 
                              object:(id)object {
    HYBObserver *obs = [[self alloc] init];
    obs.observer = observer;
    obs.selector = selector;
    obs.name = name;
    obs.object = object;
    return obs;
}

+ (instancetype)observerWithObserver:(id)observer 
                               block:(HYBNotificationBlock)block 
                               queue:(dispatch_queue_t)queue 
                                 name:(NSString *)name 
                               object:(id)object {
    HYBObserver *obs = [[self alloc] init];
    obs.observer = observer;
    obs.block = block;
    obs.queue = queue ?: dispatch_get_main_queue(); // 默認(rèn)主隊(duì)列
    obs.name = name;
    obs.object = object;
    return obs;
}
@end
2. 定義通知模型(HYBNotification.h/m)

模仿 NSNotification,封裝通知的名稱、發(fā)送者、附加信息。

// HYBNotification.h
#import <Foundation/Foundation.h>

@interface HYBNotification : NSObject
/// 通知名稱
@property (nonatomic, copy, readonly) NSString *name;
/// 發(fā)送者
@property (nonatomic, strong, readonly) id object;
/// 附加信息
@property (nonatomic, strong, readonly) NSDictionary *userInfo;

/// 初始化通知
+ (instancetype)notificationWithName:(NSString *)name 
                              object:(id)object 
                            userInfo:(NSDictionary *)userInfo;
@end

// HYBNotification.m
#import "HYBNotification.h"

@implementation HYBNotification
+ (instancetype)notificationWithName:(NSString *)name 
                              object:(id)object 
                            userInfo:(NSDictionary *)userInfo {
    HYBNotification *notification = [[self alloc] init];
    _name = name;
    _object = object;
    _userInfo = userInfo ?: @{};
    return notification;
}
@end
3. 實(shí)現(xiàn)通知中心核心類(HYBNotificationCenter.h/m)

單例設(shè)計(jì),實(shí)現(xiàn)注冊(cè)、發(fā)送、移除觀察者的核心邏輯,并保證線程安全。

// HYBNotificationCenter.h
#import <Foundation/Foundation.h>
#import "HYBNotification.h"
#import "HYBObserver.h"

@interface HYBNotificationCenter : NSObject
/// 獲取默認(rèn)通知中心(單例)
+ (instancetype)defaultCenter;

/// 注冊(cè) SEL 類型的觀察者
- (void)addObserver:(id)observer 
           selector:(SEL)selector 
               name:(NSString *)name 
             object:(id)object;

/// 注冊(cè) Block 類型的觀察者
- (id)addObserverForName:(NSString *)name 
                  object:(id)object 
                   queue:(dispatch_queue_t)queue 
              usingBlock:(HYBNotificationBlock)block;

/// 發(fā)送通知
- (void)postNotification:(HYBNotification *)notification;
- (void)postNotificationName:(NSString *)name 
                      object:(id)object;
- (void)postNotificationName:(NSString *)name 
                      object:(id)object 
                    userInfo:(NSDictionary *)userInfo;

/// 移除觀察者
- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer 
                  name:(NSString *)name 
                object:(id)object;
@end

// HYBNotificationCenter.m
#import "HYBNotificationCenter.h"
#import <objc/message.h>

@interface HYBNotificationCenter ()
/// 存儲(chǔ)觀察者:key = 通知名稱,value = 觀察者數(shù)組(HYBObserver)
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSMutableArray<HYBObserver *> *> *observerDict;
/// 線程安全鎖
@property (nonatomic, strong) NSLock *lock;
/// Block 觀察者的持有者(用于返回給外部,供移除)
@property (nonatomic, strong) NSMutableSet *blockObservers;
@end

@implementation HYBNotificationCenter
+ (instancetype)defaultCenter {
    static HYBNotificationCenter *_defaultCenter = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _defaultCenter = [[self alloc] init];
    });
    return _defaultCenter;
}

- (instancetype)init {
    if (self = [super init]) {
        _observerDict = [NSMutableDictionary dictionary];
        _lock = [[NSLock alloc] init];
        _blockObservers = [NSMutableSet set];
    }
    return self;
}

#pragma mark - 注冊(cè)觀察者
- (void)addObserver:(id)observer 
           selector:(SEL)selector 
               name:(NSString *)name 
             object:(id)object {
    if (!observer || !selector || !name) return;
    
    [self.lock lock];
    // 創(chuàng)建觀察者模型
    HYBObserver *hybObserver = [HYBObserver observerWithObserver:observer 
                                                        selector:selector 
                                                            name:name 
                                                          object:object];
    // 從字典中獲取對(duì)應(yīng)名稱的觀察者數(shù)組,不存在則創(chuàng)建
    NSMutableArray *observers = self.observerDict[name];
    if (!observers) {
        observers = [NSMutableArray array];
        self.observerDict[name] = observers;
    }
    [observers addObject:hybObserver];
    [self.lock unlock];
}

- (id)addObserverForName:(NSString *)name 
                  object:(id)object 
                   queue:(dispatch_queue_t)queue 
              usingBlock:(HYBNotificationBlock)block {
    if (!name || !block) return nil;
    
    [self.lock lock];
    // 創(chuàng)建 Block 類型的觀察者(觀察者對(duì)象為 NSObject 占位,因?yàn)?Block 無需外部觀察者)
    id placeholderObserver = [[NSObject alloc] init];
    HYBObserver *hybObserver = [HYBObserver observerWithObserver:placeholderObserver 
                                                           block:block 
                                                           queue:queue 
                                                             name:name 
                                                           object:object];
    // 存儲(chǔ)觀察者
    NSMutableArray *observers = self.observerDict[name];
    if (!observers) {
        observers = [NSMutableArray array];
        self.observerDict[name] = observers;
    }
    [observers addObject:hybObserver];
    [self.blockObservers addObject:placeholderObserver];
    [self.lock unlock];
    
    return placeholderObserver; // 返回占位對(duì)象,供外部移除
}

#pragma mark - 發(fā)送通知
- (void)postNotification:(HYBNotification *)notification {
    if (!notification || !notification.name) return;
    
    [self.lock lock];
    // 獲取該通知名稱對(duì)應(yīng)的所有觀察者
    NSMutableArray<HYBObserver *> *observers = [self.observerDict[notification.name] mutableCopy];
    [self.lock unlock];
    
    if (!observers.count) return;
    
    // 遍歷觀察者,分發(fā)通知(注意:需過濾已釋放的觀察者)
    for (HYBObserver *obs in observers) {
        // 匹配發(fā)送者:如果觀察者指定了 object,必須與通知的 object 一致
        if (obs.object && obs.object != notification.object) continue;
        
        // 觀察者已釋放,跳過
        if (!obs.observer) {
            [self _removeInvalidObserver:obs];
            continue;
        }
        
        // 分發(fā)通知:SEL 方式
        if (obs.selector) {
            // 檢查觀察者是否實(shí)現(xiàn)了該方法
            if ([obs.observer respondsToSelector:obs.selector]) {
                // 用 objc_msgSend 發(fā)送消息(需關(guān)閉 ARC 或橋接)
                ((void (*)(id, SEL, HYBNotification *))objc_msgSend)(obs.observer, obs.selector, notification);
            }
        }
        // 分發(fā)通知:Block 方式
        else if (obs.block) {
            dispatch_async(obs.queue, ^{
                obs.block(notification);
            });
        }
    }
}

- (void)postNotificationName:(NSString *)name 
                      object:(id)object {
    [self postNotificationName:name object:object userInfo:nil];
}

- (void)postNotificationName:(NSString *)name 
                      object:(id)object 
                    userInfo:(NSDictionary *)userInfo {
    HYBNotification *notification = [HYBNotification notificationWithName:name object:object userInfo:userInfo];
    [self postNotification:notification];
}

#pragma mark - 移除觀察者
- (void)removeObserver:(id)observer {
    [self removeObserver:observer name:nil object:nil];
}

- (void)removeObserver:(id)observer 
                  name:(NSString *)name 
                object:(id)object {
    [self.lock lock];
    // 如果指定了名稱,只移除該名稱下的觀察者
    if (name) {
        NSMutableArray *observers = self.observerDict[name];
        [observers filterUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(HYBObserver *obs, NSDictionary *bindings) {
            return !(obs.observer == observer && (object == nil || obs.object == object));
        }]];
    } 
    // 未指定名稱,遍歷所有通知名稱移除
    else {
        [self.observerDict enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSMutableArray *observers, BOOL *stop) {
            [observers filterUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(HYBObserver *obs, NSDictionary *bindings) {
                return !(obs.observer == observer && (object == nil || obs.object == object));
            }]];
        }];
    }
    // 移除 Block 觀察者的占位對(duì)象
    [self.blockObservers removeObject:observer];
    [self.lock unlock];
}

#pragma mark - 私有方法:移除無效觀察者
- (void)_removeInvalidObserver:(HYBObserver *)observer {
    [self.lock lock];
    NSMutableArray *observers = self.observerDict[observer.name];
    [observers removeObject:observer];
    if (observers.count == 0) {
        [self.observerDict removeObjectForKey:observer.name];
    }
    [self.lock unlock];
}
@end

4. 使用示例

// 1. 注冊(cè) SEL 類型觀察者
[[HYBNotificationCenter defaultCenter] addObserver:self 
                                          selector:@selector(handleNotification:) 
                                              name:@"HYBTestNotification" 
                                            object:nil];

// 2. 注冊(cè) Block 類型觀察者
id blockObserver = [[HYBNotificationCenter defaultCenter] addObserverForName:@"HYBTestNotification" 
                                                                     object:nil 
                                                                      queue:dispatch_get_main_queue() 
                                                                 usingBlock:^(HYBNotification *notification) {
    NSLog(@"Block 接收到通知:%@, userInfo: %@", notification.name, notification.userInfo);
}];

// 3. 發(fā)送通知
[[HYBNotificationCenter defaultCenter] postNotificationName:@"HYBTestNotification" 
                                                     object:nil 
                                                   userInfo:@{@"key": @"value"}];

// 4. 移除觀察者
[[HYBNotificationCenter defaultCenter] removeObserver:self];
[[HYBNotificationCenter defaultCenter] removeObserver:blockObserver];

// 響應(yīng)方法
- (void)handleNotification:(HYBNotification *)notification {
    NSLog(@"SEL 接收到通知:%@, userInfo: %@", notification.name, notification.userInfo);
}

三、自定義通知中心的優(yōu)化方向

  1. 通知合并:模仿 NSNotificationQueue,實(shí)現(xiàn)通知的緩沖和合并策略(如同一通知短時(shí)間內(nèi)多次發(fā)送只執(zhí)行一次)。
  2. 多播委托:結(jié)合委托模式,支持多觀察者的委托回調(diào)。
  3. 內(nèi)存優(yōu)化:定期清理無效觀察者(如通過 RunLoop 定時(shí)檢查),避免字典中存儲(chǔ)大量已釋放的觀察者。
  4. 跨進(jìn)程通知:基于 CFNotificationCenter 實(shí)現(xiàn)跨進(jìn)程的通知分發(fā)(原生 NSNotificationCenter 僅支持進(jìn)程內(nèi))。
  5. 類型安全:通過泛型或協(xié)議約束,讓通知的 userInfo 更具類型安全性。

總結(jié)

OC 原生通知中心的核心是觀察者模式 + 哈希表管理觀察者 + 線程安全的通知分發(fā);自定義通知中心時(shí),需重點(diǎn)解決弱引用、線程安全、觀察者匹配、通知分發(fā)四大問題。上述實(shí)現(xiàn)覆蓋了原生通知中心的核心功能,同時(shí)保留了擴(kuò)展性,可根據(jù)實(shí)際需求進(jìn)一步優(yōu)化。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容