在上次 NSNotificationCenter 移除通知的测试中,发现 NSNotificationCenter 的机制也是有迹可循,这次就尝试亲手实现一个具有类似功能的 NotificationCenter。
如果文中有不足之处,还烦请路过的大佬指正,谢谢!
目录
1. 拥有释放回调的自定义类 ObjectDeallocObserver
2. MyNotificationModel 存储单个通知的信息
一. 分析功能
通过查看 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的类属性
五、代码链接
源代码地址:NSNotificationCenterDemo