[iOS] NSNotificationCenter 的接口功能分析和模拟实现

在上次 NSNotificationCenter 移除通知的测试中,发现 NSNotificationCenter 的机制也是有迹可循,这次就尝试亲手实现一个具有类似功能的 NotificationCenter。

如果文中有不足之处,还烦请路过的大佬指正,谢谢!


目录

一. 分析功能

1. 单例对象

2. 添加通知

3. 发送通知

4. 移除通知

二. 方案设计

1. 存储结构

2.  自动移除通知

三、方案实现

1. 拥有释放回调的自定义类 ObjectDeallocObserver

2. MyNotificationModel 存储单个通知的信息

3. 双向链表

(1) 链表节点

 (2) 双向链表实现

4. MyNotificationCenter 对外接口

4. MyNotificationCenter 接口实现

(1) 创建存储结构

(2) 单例模式实现 defaultCenter

(3) 添加通知

(4) 发送通知

(5) 移除通知

四、实现总结

五、代码链接


一. 分析功能

通过查看 NSNotificationCenter 的 API,可以分析出 NSNotificationCenter 主要有下面几个功能:

1. 单例对象

NSNotificationCenter 使用 class 属性修饰的 defaultCenter 访问全局唯一的通知中心。

2. 添加通知

(1) 添加通知有两种方式,SEL 和 block。

(2) 添加通知时,可选添加 name 和 object。

(3) 当 name 为空时,可以接收所有 name 的通知;当 object 为空,可以接收所有 object 的通知。

(4) 当 name 不为空时,只能接收指定 name 的通知;当 object 不为空时,只能接收指定 object 的通知。

(5) 相同的 name 和 object 被重复添加时,每一个被添加的通知都可以被响应。 

3. 发送通知

(1) 发送通知时,name 不能为空。name 为空时,没有通知接收者。

(2) 发送通知时,当发送者的 object 为空时,只有接收通知的 name 相同且 object 也为空,才能接到通知。

(3) 发送通知时,若发送者的 object 不为空时,只要接收通知的 name 相同或者 object 相同,就能接到通知。

4. 移除通知

(1) 移除通知有两种方式,removeObserver: 和 removeObserver:name:object:。

(2) 移除通知时,observer 必须相同。

(3) 移除通知和添加通知的参数规则相同。空可以匹配所有值,非空则必须相同。

(4) 使用 SEL 添加的通知,当接收者被释放时,通知自动被移除;使用 Block 添加的通知,必须手动移除。

二. 方案设计

通过上述的功能说明,尝试一下设计通知中心。

1. 存储结构

因为通过 name 和 object 寻找对应的通知,相同 name 和相同 object 的通知可以重复添加,第一想法便是使用字典+字典+数组来存储所有的通知接收者信息。

使用 name 和 object 作为字典的 Key 值,会存在一个问题。由于 name 和 object 可以为空,但是字典的 Key 值不可以为空。因此,当 name 或者 object 为空时,使用 NSNull 替代空值作为 Key 值

相同 name 和相同 object 的通知,可以使用数组来存储。但是考虑到添加、发送、删除通知的过程中,主要的操作为在末尾添加、遍历所有对象和遍历对象时删除相同 observer 的对象。这些操作更多的是增删遍历,并没有发挥出数组通过下标快速查找的优点,因此数组替换为链表实现。同时,考虑到需要删除通知对象,为了操作简单,使用双向链表实现。

综上所述,最终决定使用字典+字典+双向链表的结构来存储每一个通知。

每个通知需要存储关键的参数。根据添加通知时的参数,既需要存储 SEL 方式添加的参数,也需要存储 block 方式添加的参数。此外,再存储通知的 name 、object 和接收通知的 observer 。剩下的 observerDeallocBlock 为 observer 自动释放时的回调,具体实现方式稍后再谈。

大概的设计结构图如下(Notification Model中的“+”表示公有变量):

2.  自动移除通知

这个功能算是实现通知中心中的难点。接受通知者 observer 在释放前,需要自动移除保存在通知中心中的通知。

由于 NSObject 对象的释放由系统控制,无法获取到每一个对象释放的时机。当然,也可以手动在每一个 observer 的 dealloc 中添加释放的通知。但这种方案的问题也显而易见,每一个需要接受通知的对象,都需要在 dealloc 函数中添加额外的代码,过于繁琐。

因此,这个功能实现的核心是,如何知道接收通知的对象将要被释放

在本人最初的想法中,可以通过给 NSObject 添加一个 category ,实现 dealloc 函数。这方法是不可行的,category 中的 dealloc 会覆盖原 NSObject 类的 dealloc,导致造成预料之外的错误。

既然如此,就有了第二方案。在添加通知时,可以通过 Method Swizzling 交换 observer 的 dealloc 函数,在交换后的实现中添加释放前的回调,让通知中心可以获取到对象的释放时机。但这种方案也存在一些问题。

一是当同一个对象被多次添加通知,导致多次交换。这个可以使用集合存储已经被交换过 dealloc 实现的对象,以免对同一对象造成二次交换。此处不能使用字典的原因是,字典的 Key 值的内存管理类型是 copy,不能保证所有被通知的对象 observer 都实现了 NSCopying。

二是交换之后,也需要使用 delegate 或者 block 回调来告诉通知中心该对象被释放。为了减少代码量,可以在通知中心中添加通知时,使用关联对象给 observer 动态添加 block 的方式实现。即使如此,在添加通知时对 observer 的 dealloc 释放之后,不能保证在项目中其它地方、其他开发者不对这个函数进行交换。

在前两种方案都被否定的情况下,考虑到了关联对象的自动释放特性。简答来说,当被关联的对象被释放时,系统会自动释放关联对象。

那么,只需要给 observer 添加一个关联对象,若关联对象被释放了,就表示 observer 也被释放了。

关联对象自动释放的实现,涉及到 runtime 中对象释放的知识,顺便在文中也记录一下 runtime 源代码 的地址。下载并解压链接中的最新版本,在 runtime 文件夹中找到 objc-runtime-new.mm 文件中的 objc_destructInstance 函数就能看到一个对象释放的过程。关联对象的释放实现,在 objc-references.mm 文件中的 _object_remove_assocations 函数。

查看源代码就能看到,当存在关联对象时,系统会在释放对象时,通过遍历 AssociationsManager 中键值对的方式,移除所有绑定在该对象上的关联对象 。

/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory. 
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
**********************************************************************/
void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}


/***********************************************************************
* object_dispose
* fixme
* Locking: none
**********************************************************************/
id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}
// Unlike setting/getting an associated reference,
// this function is performance sensitive because of
// raw isa objects (such as OS Objects) that can't track
// whether they have associated objects.
void
_object_remove_assocations(id object)
{
    ObjectAssociationMap refs{};

    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            refs.swap(i->second);
            associations.erase(i);
        }
    }

    // release everything (outside of the lock).
    for (auto &i: refs) {
        i.second.releaseHeldValue();
    }
}

 

三、方案实现

1. 拥有释放回调的自定义类 ObjectDeallocObserver

这个类的主要作用,就是用来作为关联对象被绑定到另一对象上。被绑定的对象释放时,也会自动释放被绑定对象上的关联对象。那么,只需要在这个关联对象类的 dealloc 中添加一个释放时的 block 回调即可。

// -------- ObjectDeallocObserver.h --------
/// 通过关联对象,获得对象的释放事件
@interface ObjectDeallocObserver : NSObject

@property(nonatomic, copy) typeof(void(^)(void)) deallocBlock;

@end

// -------- ObjectDeallocObserver.m --------
@implementation ObjectDeallocObserver

- (void)dealloc {
    if (_deallocBlock) {
        _deallocBlock();
        _deallocBlock = nil;
    }
    NSLog(@"%s", __FUNCTION__);
}

@end

2. MyNotificationModel 存储单个通知的信息

因为添加通知有 SEL 和 block 两种方式,保存通知的对象,既能保存 SEL,也能保存 block。因此,可以先建立一个 MyNotificationModel 类来存储通知的相关信息。

需要注意的是,observer 的内存管理类型是 weak。如果 observer 为 strong 时,会使通知中心强引用 observer ,导致 observer 无法释放。

当设置 observer 时,通过重写 setter 方法,为这个 observer 绑定一个可以获取释放回调的关联对象。通过关联对象的释放回调,可以获取到 observer 的释放时机。当 observer 释放时,从通知中心移除这个通知,即可实现 observer 释放时自动移除通知的功能。

// -------- MyNotificationModel.h --------
@interface MyNotificationModel : NSObject

/// 通知的 name
@property(nonatomic, copy, nullable) NSString *name;

/// 通知的 object
@property(nonatomic, strong, nullable) id object;

// MASK: 此处使用 weak 属性修饰,以免 observer 被通知中心持有,导致无法释放
/// 通知的发送对象 observer
@property(nonatomic, weak) id observer;

/// 通知的调用函数 selector
@property(nonatomic, assign) SEL selector;

/// 通知的回调函数 block
@property(nonatomic, copy) typeof(void(^)(NSNotification *)) block;

/// 通知回调函数的执行队列 queue
@property(nonatomic, strong, nullable) NSOperationQueue *queue;

/// 通知的发送对象 observer 释放时的回调
@property(nonatomic, copy) typeof(void(^)(MyNotificationModel *)) observerDeallocBlock;

@end

// -------- MyNotificationModel.m --------
// 使用关联对象需要导入 <objc/runtime.h>
@implementation MyNotificationModel

- (void)setObserver:(id)observer {
    _observer = observer;
    
    if (_observer) {
        ObjectDeallocObserver *deallocObserver = [ObjectDeallocObserver new];
        __weak typeof(self) weakSelf = self;
        deallocObserver.deallocBlock = ^{
            if (weakSelf.observerDeallocBlock) {
                weakSelf.observerDeallocBlock(weakSelf);
            }
        };
        
        ObjectDeallocObserver *deallocObserver = [ObjectDeallocObserver new];
        __weak typeof(self) weakSelf = self;
        deallocObserver.deallocBlock = ^{
            if (weakSelf.observerDeallocBlock) {
                weakSelf.observerDeallocBlock(weakSelf);
            }
        };
        
        // 使用UUID保证当同一对象添加多个通知时,不会因为 Key 相同导致覆盖之前的关联对象
        NSString *key = [NSString stringWithFormat:@"DeallocBlock_%@", [[NSUUID UUID] UUIDString]];
        
        // MASK: 由于关联对象在被关联对象释放时,自动释放,可以通过关联对象的释放获取到被关联对象的释放时机
        objc_setAssociatedObject(_observer, [key UTF8String], deallocObserver, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
}

@end

3. 双向链表

(1) 链表节点

链表的基础是链表节点,节点中存储一个 MyNotificationModel 对象即可。

// -------- TwoWayLinkedListNode.h --------
/// 双向链表节点
@interface TwoWayLinkedListNode<ObjectType> : NSObject

/// 节点值
@property(nonatomic, strong) ObjectType value;
/// 后一节点
@property(nonatomic, strong, nullable) TwoWayLinkedListNode *nextNode;
/// 前一节点
@property(nonatomic, weak, nullable) TwoWayLinkedListNode *previousNode;


+ (instancetype)nodeWithValue:(__nonnull ObjectType)value;

@end

// -------- TwoWayLinkedListNode.m --------
@implementation TwoWayLinkedListNode

+ (instancetype)nodeWithValue:(__nonnull id)value {
    TwoWayLinkedListNode *node = [[TwoWayLinkedListNode alloc] init];
    node.value = value;
    return node;
}

@end

 (2) 双向链表实现

根据上述的功能分析,双向链表主要需要实现的功能至少有以下三个:

① 在链表的末尾添加新的节点

② 遍历所有的链表节点

③ 遍历所有的链表节点并删除指定节点

// -------- TwoWayLinkedList.h --------
/// 双向链表
@interface TwoWayLinkedList<ObjectType> : NSObject

/// 链表的节点数量
@property(nonatomic, assign, readonly) NSUInteger count;


/// 在双向链表末尾添加节点,节点值为value
- (void)addObjectWithValue:(__nonnull ObjectType)value;

/// 遍历所有节点
- (void)enumerateObjectsUsingBlock:(void(^)(ObjectType value))block;

/// 根据判断条件移除
- (void)removeObjectsWithCondition:(BOOL(^)(ObjectType value))condition;

/// 移除链表中所有节点
- (void)removeAllObjects;

@end
// -------- TwoWayLinkedList.m --------
@interface TwoWayLinkedList ()

/// 链表头结点
@property(nonatomic, strong) TwoWayLinkedListNode<id> *head;

/// 链表尾结点
@property(nonatomic, strong) TwoWayLinkedListNode<id> *tail;

@end


@implementation TwoWayLinkedList

- (instancetype)init {
    self = [super init];
    if (self) {
        _count = 0;
    }
    return self;
}

#pragma mark - Public Function

/// 在双向链表末尾添加节点,节点值为value
- (void)addObjectWithValue:(__nonnull id)value {
    TwoWayLinkedListNode *node = [TwoWayLinkedListNode nodeWithValue:value];
    
    if (_tail) {
        _tail.nextNode = node;
        node.previousNode = self.tail;
        _tail = node;
    } else {
        _head = node;
        _tail = node;
    }
    
    ++ _count;
}

/// 遍历所有节点
- (void)enumerateObjectsUsingBlock:(void(^)(id value))block {
    TwoWayLinkedListNode *node = _head;
    while (node) {
        block(node.value);
        
        node = node.nextNode;
    }
}

/// 根据判断条件移除
- (void)removeObjectsWithCondition:(BOOL(^)(id value))condition {
    if (!_head) {
        return;
    }
    
    TwoWayLinkedListNode *node = _head;
    while (node) {
        if (condition(node.value)) {
            if (node == _head) {
                _head = node.nextNode;
            }
            
            if (node == _tail) {
                _tail = node.previousNode;
            }
            
            node.previousNode.nextNode = node.nextNode;
            node.nextNode.previousNode = node.previousNode;
            
            -- _count;
        }
        node = node.nextNode;
    }
}

/// 移除链表中所有节点
- (void)removeAllObjects {
    TwoWayLinkedListNode *node = _head.nextNode;
    while (node) {
        node.previousNode.nextNode = nil;
    }
    
    _head = nil;
    _tail = nil;
    _count = 0;
}


#pragma mark - description

- (NSString *)description {
    if (!_head) {
        return @"[]";
    }
    
    NSMutableString *string = [NSMutableString stringWithFormat:@"[%@", _head.value];
    TwoWayLinkedListNode *node = _head.nextNode;
    while (node) {
        [string appendFormat:@"%@", [NSString stringWithFormat:@", %@", node.value]];
        node = node.nextNode;
    }
    [string appendString:@"]"];
    return [string description];
}

- (NSString *)debugDescription {
    return [self description];
}

4. MyNotificationCenter 对外接口

MyNotificationCenter 的对外接口可以完全照搬 NSNotificationCenter,直接 copy 即可。

不过,在查看 NSNotificationCenter 对外接口的过程中,可以发现 NSNotificationCenter 的 defaultCenter 只是一个属性,其中属性修辞为 @property(class, readonly, strong)。其中 readonly 只读、strong 强引用比较好理解,class 属性则接触比较少,一般在官方 API 中常见,比如说 UIApplication、NSURLSession、NSUserDefaults 等等。

class 属性的实现方式和单例模式完全一样。

// -------- MyNotificationCenter.h --------
@interface MyNotificationCenter : NSObject

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

//+ (MyNotificationCenter *)defaultCenter;

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

- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

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

@end

4. MyNotificationCenter 接口实现

(1) 创建存储结构

传入的通知有两个参数,name 和 object。这两个参数都有可能为空。如果 name 或者 object 为空时,则以 NSNull 替代作为 Key 值。

@interface MyNotificationCenter ()

// MASK: 此处使用双向链表,因为不需要通过下标访问,增删操作比较多,基本没有查找操作
@property(nonatomic, strong) NSMutableDictionary<id, NSMutableDictionary<id, TwoWayLinkedList<MyNotificationModel *> *> *> *notificationDictionary;

// MASK: 使用信号量作为线程安全的保障
@property(nonatomic, strong) dispatch_semaphore_t semaphore;

@end

(2) 实现 defaultCenter

虽然使用 class 属性修饰了 defaultCenter ,看起来有些陌生,但本质上实现方式和单例模式一模一样。iOS 下单例模式的实现就不多说了,只能说 GCD 牛逼!

不过,GCD 的 dispatch_once 能很简单地实现线程安全的单例模式,但是还要注意到使用 alloc + init 也能初始化新的对象,而且这个对象和使用单例创建的对象并不是一个。简单来说,只使用了 dispatch_once 的“单例”对象,还没有完全实现单例模式

如何完善呢?

重写一下 allocWithZone: 方法即可。

# pragma mark - 单例模式实现 defaultCenter

+ (MyNotificationCenter *)defaultCenter {
    static MyNotificationCenter *_instance = nil;
    static dispatch_once_t token;
    dispatch_once(&token, ^{
        // 此处如果写 [self alloc] 或者 [super alloc] 都不行
        // [self alloc] 是因为重写 allocWithZone: 方法,会陷入死循环
        // [super alloc] 是因为当子类中有同名方法时,先调用子类的方法
        _instance = [[super allocWithZone:nil] init];
    });
    return _instance;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    return [MyNotificationCenter defaultCenter];
}

// MASK: 以免 copy 导致单例对象的地址发生改变
- (id)copyWithZone:(nullable NSZone *)zone {
    return [MyNotificationCenter defaultCenter];
}

- (instancetype)init {
    self = [super init];
    if (self) {
        _semaphore = dispatch_semaphore_create(1);
    }
    return self;
}

完善后的测试结果:

MyNotificationCenter *center1 = [MyNotificationCenter defaultCenter];
MyNotificationCenter *center2 = [[MyNotificationCenter alloc] init];
MyNotificationCenter *center3 = [MyNotificationCenter new];

// ---- 控制台调试输出 ----
// Printing description of center1:
// (MyNotificationCenter *) center1 = 0x0000000282900500
// Printing description of center2:
// (MyNotificationCenter *) center1 = 0x0000000282900500
// Printing description of center3:
// (MyNotificationCenter *) center1 = 0x0000000282900500

PS:不过 NSNotificationCenter 的 defaultCenter 没有进行优化,尚不知原因。

NSNotificationCenter *center1 = [[NSNotificationCenter alloc] init];
NSNotificationCenter *center2 = [NSNotificationCenter new];
NSNotificationCenter *center3 = [NSNotificationCenter defaultCenter];

// 控制台打印类型和地址。使用调试时的 po 指令,可以看到 defaultCenter 中拥有更多的通知
// center1: NSNotificationCenter, 0x281d99180
// center2: NSNotificationCenter, 0x281d988c0
// center3: NSNotificationCenter, 0x281d92300

(3) 添加通知

添加通知的方法虽然有两种 SEL 和 block,但是可以本质都是将 SEL 和 block 转换为 MyNotificationModel 存储起来。

SEL 添加比较简单,只需要将相关信息存入 MyNotificationModel 对象中即可。

此处需要注意的是,使用 Block 添加的通知,observer 为手动创建,需要函数结束时返回创建的 observer。但是这个 observer 的本质是什么呢?

通过查看 NSNotificationCenter 添加 Block 通知时的调试结果,可以看到,NSNotificationCenter 将封装好通知接收者相关信息的对象作为函数返回值。

某种程序上,__NSObserver 和 MyNotificationModel 对象的作用相似。因此,可以将添加通知时,构造的 MyNotificationModel 对象作为函数返回值。

需要注意的是,由于 MyNotificationModel 的 observer 就是自身,而 MyNotificationCenter 会强引用 MyNotificationModel,因此使用 Block 添加的通知无法自动移除,必须手动移除通知

另外,用于使用了 Block 了,必须要考虑到循环引用的问题。在添加通知时,MyNotificationModel 会强引用 Block,并且作为函数返回值返回。如果外部代码不注意编码规范,使 Block 直接或间接强引用 MyNotificationModel,就会形成引用环,导致出现内存泄漏的问题。

#pragma mark - Public Function 添加通知

- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject {
    MyNotificationModel *model = [MyNotificationModel new];
    model.observer = observer;
    model.selector = aSelector;
    model.name = aName;
    model.object = anObject;
    
    __weak typeof(self) weakSelf = self;
    model.observerDeallocBlock = ^(MyNotificationModel *model) {
        [weakSelf removeObserver:model.observer name:model.name object:model.object];
    };
    
    [self addObserverWithName:aName object:anObject model:model];
}

- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block {
    // 由于通过该方法添加的通知没有 observer,将 MyNotificationModel 返回,用来移除通知
    MyNotificationModel *model = [MyNotificationModel new];
    model.observer = model;
    model.name = name;
    model.object = obj;
    model.block = block;
    model.queue = queue;
    
    // 由于 observer 就是自身,而 MyNotificationCenter 强引用 MyNotificationModel,因此使用 Block 添加的通知无法自动移除
//    __weak typeof(self) weakSelf = self;
//    model.observerDeallocBlock = ^(MyNotificationModel *model) {
//        [weakSelf removeObserver:model.observer name:model.name object:model.object];
//    };
    
    [self addObserverWithName:name object:obj model:model];
    
    return model;
}

#pragma mark - Private Function 添加通知

/// 添加通知
- (void)addObserverWithName:(nullable NSNotificationName)name object:(nullable id)obj model:(MyNotificationModel *)model {
    dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
    
    // 若 name 或 obj 为空时,使用 NSNull 替代
    id nameKey = name;
    if (!nameKey) {
        nameKey = [NSNull null];
    }
    id objKey = obj;
    if (!objKey) {
        objKey = [NSNull null];
    }
    
    // 使用懒加载初始化
    if (!_notificationDictionary[nameKey]) {
        _notificationDictionary[nameKey] = [NSMutableDictionary dictionary];
    }
    if (!_notificationDictionary[nameKey][objKey]) {
        _notificationDictionary[nameKey][objKey] = [[TwoWayLinkedList alloc] init];
    }
    
    // 加入通知序列中
    [_notificationDictionary[nameKey][objKey] addObjectWithValue:model];
    
    dispatch_semaphore_signal(_semaphore);
}

(4) 发送通知

发送通知有三种方法,本质也是一致的,把 name 和 object 转换为 NSNotification,统一调用即可。

当 name 为空时,通知不可发送。只要根据 object 的状态,遍历响应链表中的节点发送通知即可。

#pragma mark - Public Function 发送通知

- (void)postNotification:(NSNotification *)notification {
    dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
    
    if (!notification.name) {
        return;
    }
    
    [self sendNotification:notification modelList:_notificationDictionary[[NSNull null]][[NSNull null]]];
    [self sendNotification:notification modelList:_notificationDictionary[notification.name][[NSNull null]]];
    
    if (notification.object) {
        [self sendNotification:notification modelList:_notificationDictionary[[NSNull null]][notification.object]];
        [self sendNotification:notification modelList:_notificationDictionary[notification.name][notification.object]];
    }
    
    dispatch_semaphore_signal(_semaphore);
}

- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject {
    [self postNotification:[NSNotification notificationWithName:aName object:anObject]];
}

- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo {
    [self postNotification:[NSNotification notificationWithName:aName object:anObject userInfo:aUserInfo]];
}


#pragma mark - Private Function 发送通知

/// 向序列中的所有接收者发送通知
- (void)sendNotification:(NSNotification *)notification modelList:(TwoWayLinkedList *)list {
    [list enumerateObjectsUsingBlock:^(MyNotificationModel * _Nonnull model) {
        // 当 observer 不为空,且 observer 能响应 selector 时,调用 SEL,否则调用 block
        if (model.observer && [model.observer respondsToSelector:model.selector]) {
            if ([NSStringFromSelector(model.selector) hasSuffix:@":"]) {
                [model.observer performSelector:model.selector withObject:notification];
            } else {
                [model.observer performSelector:model.selector];
            }
        } else if (model.block) {
            if (model.queue) {
                NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
                    model.block(notification);
                }];
                [model.queue addOperation:op];
            } else {
                model.block(notification);
            }
        }
    }];
}

(5) 移除通知

移除通知时,需要根据传入的 name 和 object 类型,去遍历所有的通知,删除其中相同 observer 的通知即可。

需要注意的是,如果一个链表的所有节点都已经被删除了,最好从字典中删除这个键值对,节省空间。

#pragma mark - Public Function 移除通知

- (void)removeObserver:(id)observer {
    [self removeObserver:observer name:nil object:nil];
}

- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject {
    dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
    
    if (!aName && !anObject) {
        [_notificationDictionary enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, NSMutableDictionary<id,TwoWayLinkedList<MyNotificationModel *> *> * _Nonnull dictionary, BOOL * _Nonnull stop) {
            [dictionary enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, TwoWayLinkedList<MyNotificationModel *> * _Nonnull list, BOOL * _Nonnull stop) {
                [list removeObjectsWithCondition:^BOOL(MyNotificationModel * _Nonnull value) {
                    return value.observer == observer;
                }];
                
                if (list.count == 0) {
                    [dictionary removeObjectForKey:key];
                }
            }];
            
            if ([dictionary count] == 0) {
                [_notificationDictionary removeObjectForKey:key];
            }
        }];
    } else if (aName && !anObject) {
        [_notificationDictionary[aName] enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, TwoWayLinkedList<MyNotificationModel *> * _Nonnull list, BOOL * _Nonnull stop) {
            [list removeObjectsWithCondition:^BOOL(MyNotificationModel * _Nonnull value) {
                return value.observer == observer;
            }];
            
            if (list.count == 0) {
                [_notificationDictionary[aName] removeObjectForKey:key];
            }
        }];
        
        if ([_notificationDictionary[aName] count] == 0) {
            [_notificationDictionary removeObjectForKey:aName];
        }
    } else if (!aName && anObject) {
        [_notificationDictionary enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, NSMutableDictionary<id,TwoWayLinkedList<MyNotificationModel *> *> * _Nonnull dictionary, BOOL * _Nonnull stop) {
            [dictionary[anObject] removeObjectsWithCondition:^BOOL(MyNotificationModel * _Nonnull value) {
                return value.observer == observer;
            }];
            
            if ([dictionary[anObject] count] == 0) {
                [dictionary removeObjectForKey:key];
            }
            
            if ([dictionary count] == 0) {
                [_notificationDictionary removeObjectForKey:key];
            }
        }];
    } else {
        [_notificationDictionary[aName][anObject] removeObjectsWithCondition:^BOOL(MyNotificationModel * _Nonnull value) {
            return value.observer == observer;
        }];
        
        if ([_notificationDictionary[aName][anObject] count] == 0) {
            [_notificationDictionary[aName] removeObjectForKey:anObject];
            
            if ([_notificationDictionary[aName] count] == 0) {
                [_notificationDictionary removeObjectForKey:aName];
            }
        }
    }
    
    dispatch_semaphore_signal(_semaphore);
}

四、实现总结

实现一个通知中心的难度并不算特别大,只要考虑好添加、发送和移除的逻辑即可。其中,可以使用 NSNull 来替代 nil 作为 Key 值,也算是一个亮点。

实现的过程中,仅有称得上的难点,便是利用关联对象自动释放的特点,实现对象释放时自动移除通知的功能。

最后,感谢以下博客对本文的启发和帮助:

Swift 3.0 令人兴奋,但Objective-C也有小改进--Objective-C的类属性

iOS界的毒瘤-MethodSwizzling

五、代码链接

源代码地址:NSNotificationCenterDemo

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值