写在前面
iOS开发中,很多情况下会使用到通知,通知的好处很多,但是也有很多坑点,一旦没有管理好,就会造成很多莫名其妙的bug。既然通知使用不当很容易出现问题,那有没有什么办法来避免?经过思考后,决定使用block回调的方式来实现通知,并且避免掉通知的弊端。
原理
参考通知原理,采用单例全局管理,单例持有一个字典,字典中储存所有添加的block,在调用block的时候从字典中取出对应的block调用。
1.完整的单例创建
单例创建需要考虑到各种初始化方法以及拷贝,还有线程安全。
2.保证观察者生命周期不受单例影响
因为是单例持有的字典,就会造成block得不到释放,从而引起一系列问题。这里采用NSMapTable来储存block,NSMapTable使用强引用key,弱引用value,这样做的好处在于,当其中储存的对象销毁后,会自动从NSMapTable移除。使用NSMapTable可以保证生命周期不受单例影响。
3.观察者和block绑定
为了使用简单,并且保证block生命周期和观察者一样,使用RunTime动态绑定,将block和观察者绑定起来。
4.保证一个对象只添加一次观察者
多次添加观察者会造成调用的时候响应多次,这里采用对象内存地址和标识符作为字典的key,保证一个对象只添加一次。
5.多线程安全
这里采用GCD信号量来保证线程安全
6.block循环引用
对于block循环引用,这里采用回调观察者替代self,保证不会循环引用
代码
CLActionManager.h实现代码
typedef NS_ENUM(NSUInteger, CLActionType) {
CLActionColorChange,///<颜色变化
CLActionTextChange,///<文字变化
CLActionImageChange,///<图片变化
};
@interface CLActionManager : NSObject
/*
所有响应block生命周期和观察者对象生命周期一样,一个对象多次添加同一类型或者同一标识符的观察者,只会添加最后一次,响应的block回掉会随着观察者对象销毁自动销毁,建议使用枚举管理所有标识符
*/
/**
根据类型添加观察者
@param observer 观察者
@param actionType 响应类型
@param block 数据回掉
*/
+ (void)addObserver:(id)observer actionType:(CLActionType)actionType mainThread:(BOOL)mainThread block:(void(^)(id observer, NSDictionary *dictionary))block;
/**
根据类型调用
@param dictionary 数据
@param actionType 响应类型
*/
+ (void)actionWithDictionary:(NSDictionary *)dictionary actionType:(CLActionType)actionType;
//------------------------------------字符串作为唯一标识符,内部已经处理,不会和上面枚举方式冲突-------------------------------------
/**
根据标识符添加观察者
@param observer 观察者
@param identifier 标识
@param mainThread 是否在主线程回掉
@param block 数据回掉
*/
+ (void)addObserver:(id)observer identifier:(NSString *)identifier mainThread:(BOOL)mainThread block:(void(^)(id observer, NSDictionary *dictionary))block;
/**
根据标识符调用
@param dictionary 数据
@param identifier 标识符
*/
+ (void)actionWithDictionary:(NSDictionary *)dictionary identifier:(NSString *)identifier;
CLActionManager.m实现代码
#import "CLActionManager.h"
#import <objc/message.h>
@interface CLActionManager ()
@property (nonatomic, strong) NSMapTable *observerMapTable;
@property (nonatomic, strong) NSMapTable *blockKeyMapTable;
@property (nonatomic, strong) NSMapTable *mainThreadKeyMapTable;
@property (nonatomic, strong) dispatch_semaphore_t semaphore;
@end
@implementation CLActionManager
//第1步: 存储唯一实例
static CLActionManager *_manager = nil;
//第2步: 分配内存空间时都会调用这个方法. 保证分配内存alloc时都相同.
+ (id)allocWithZone:(struct _NSZone __unused*)zone {
return [self sharedManager];
}
//第3步: 保证init初始化时都相同
+ (instancetype)sharedManager {
//调用dispatch_once保证在多线程中也只被实例化一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_manager = [[super allocWithZone:NULL] init];
});
return _manager;
}
- (id)init {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_manager = [super init];
//弱引用value,强引用key
self.observerMapTable = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableWeakMemory];
self.blockKeyMapTable = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableWeakMemory];
self.mainThreadKeyMapTable = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableWeakMemory];
//信号
self.semaphore = dispatch_semaphore_create(0);
dispatch_semaphore_signal(self.semaphore);
});
return _manager;
}
//第4步: 保证copy时都相同
- (id)copyWithZone:(NSZone __unused*)zone {
return _manager;
}
//第五步: 保证mutableCopy时相同
- (id)mutableCopyWithZone:(NSZone __unused*)zone {
return _manager;
}
+ (void)addObserver:(id)observer actionType:(CLActionType)actionType mainThread:(BOOL)mainThread block:(void(^)(id observer, NSDictionary *dictionary))block {
//增加信号保证线程安全
dispatch_semaphore_wait([CLActionManager sharedManager].semaphore, DISPATCH_TIME_FOREVER);
//内存地址+key,使用内存地址保证一个对象只监听一次,key保证是同一类型
NSString *key = [NSString stringWithFormat:@"%@-%@",[NSString stringWithFormat:@"%p",observer], [[self keyWithActionType:actionType] stringByAppendingString:@"-1"]];
NSString *actionBlock = [key stringByAppendingString:@"-CLActionBlock-1"];
NSString *actionMainThread = [key stringByAppendingString:@"-CLActionMainThread-1"];
[[CLActionManager sharedManager].observerMapTable setObject:observer forKey:key];
[[CLActionManager sharedManager].blockKeyMapTable setObject:actionBlock forKey:key];
[[CLActionManager sharedManager].mainThreadKeyMapTable setObject:actionMainThread forKey:key];
//动态设置block
objc_setAssociatedObject(observer, CFBridgingRetain(actionBlock), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
//动态设置是否主线程
objc_setAssociatedObject(observer, CFBridgingRetain(actionMainThread), [NSNumber numberWithBool:mainThread], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
dispatch_semaphore_signal([CLActionManager sharedManager].semaphore);
}
+ (void)actionWithDictionary:(NSDictionary *)dictionary actionType:(CLActionType)actionType {
dispatch_semaphore_wait([CLActionManager sharedManager].semaphore, DISPATCH_TIME_FOREVER);
//key数组
NSArray<NSString *> *keyArray = [[[CLActionManager sharedManager].observerMapTable keyEnumerator] allObjects];
//匹配出对应key
NSString *identifier = [[self keyWithActionType:actionType] stringByAppendingString:@"-1"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF ENDSWITH %@",identifier];
NSArray<NSString *> *array = [keyArray filteredArrayUsingPredicate:predicate];
//遍历查找所有key
for (NSString *key in array) {
NSString *actionBlock = [[CLActionManager sharedManager].blockKeyMapTable objectForKey:key];
NSString *actionMainThread = [[CLActionManager sharedManager].mainThreadKeyMapTable objectForKey:key];
//找出对应类型的观察者
id observer = [[CLActionManager sharedManager].observerMapTable objectForKey:key];
//取出block
void(^block)(id observer, NSDictionary *dictionary) = objc_getAssociatedObject(observer, CFBridgingRetain(actionBlock));
BOOL mainThread = [(NSNumber *)objc_getAssociatedObject(observer, CFBridgingRetain(actionMainThread)) boolValue];
//block存在并且是对应方法添加,调用block
if (block) {
if (mainThread) {
//主线程
dispatch_async(dispatch_get_main_queue(), ^{
block(observer, dictionary);
});
}else {
//子线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
block(observer, dictionary);
});
}
}
}
dispatch_semaphore_signal([CLActionManager sharedManager].semaphore);
}
+ (NSString *)keyWithActionType:(CLActionType)actionType {
NSString *key;
switch (actionType) {
case CLActionTextChange:
key = @"CLActionTextChange";
break;
case CLActionColorChange:
key = @"CLActionColorChange";
break;
case CLActionImageChange:
key = @"CLActionImageChange";
break;
}
return key;
}
+ (void)addObserver:(id)observer identifier:(NSString *)identifier mainThread:(BOOL)mainThread block:(void(^)(id observer, NSDictionary *dictionary))block {
//增加信号保证线程安全
dispatch_semaphore_wait([CLActionManager sharedManager].semaphore, DISPATCH_TIME_FOREVER);
//内存地址+key,使用内存地址保证一个对象只监听一次,key保证是同一类型
NSString *key = [NSString stringWithFormat:@"%@-%@",[NSString stringWithFormat:@"%p",observer], [identifier stringByAppendingString:@"-0"]];
NSString *actionBlock = [key stringByAppendingString:@"-CLActionBlock-0"];
NSString *actionMainThread = [key stringByAppendingString:@"-CLActionMainThread-0"];
[[CLActionManager sharedManager].observerMapTable setObject:observer forKey:key];
[[CLActionManager sharedManager].blockKeyMapTable setObject:actionBlock forKey:key];
[[CLActionManager sharedManager].mainThreadKeyMapTable setObject:actionMainThread forKey:key];
//动态设置block
objc_setAssociatedObject(observer, CFBridgingRetain(actionBlock), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
//动态设置是否主线程
objc_setAssociatedObject(observer, CFBridgingRetain(actionMainThread), [NSNumber numberWithBool:mainThread], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
dispatch_semaphore_signal([CLActionManager sharedManager].semaphore);
}
+ (void)actionWithDictionary:(NSDictionary *)dictionary identifier:(NSString *)identifier {
dispatch_semaphore_wait([CLActionManager sharedManager].semaphore, DISPATCH_TIME_FOREVER);
//key数组
NSArray<NSString *> *keyArray = [[[CLActionManager sharedManager].observerMapTable keyEnumerator] allObjects];
//匹配出对应key
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF ENDSWITH %@",[identifier stringByAppendingString:@"-0"]];
NSArray<NSString *> *array = [keyArray filteredArrayUsingPredicate:predicate];
//遍历查找所有key
for (NSString *key in array) {
NSString *actionBlock = [[CLActionManager sharedManager].blockKeyMapTable objectForKey:key];
NSString *actionMainThread = [[CLActionManager sharedManager].mainThreadKeyMapTable objectForKey:key];
//找出对应类型的观察者
id observer = [[CLActionManager sharedManager].observerMapTable objectForKey:key];
//取出block
void(^block)(id observer, NSDictionary *dictionary) = objc_getAssociatedObject(observer, CFBridgingRetain(actionBlock));
BOOL mainThread = [(NSNumber *)objc_getAssociatedObject(observer, CFBridgingRetain(actionMainThread)) boolValue];
//block存在并且是对应方法添加,调用block
if (block) {
if (mainThread) {
//主线程
dispatch_async(dispatch_get_main_queue(), ^{
block(observer, dictionary);
});
}else {
//子线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
block(observer, dictionary);
});
}
}
}
dispatch_semaphore_signal([CLActionManager sharedManager].semaphore);
}
@end
改进
###1.添加监听
根据不同的观察者和监听类型动态生成Key,使用NSMapTable将对应Key和观察者设置的属性储存。+ (void)addObserver:(id)observer type:(CLActionType)type mainThread:(BOOL)mainThread actionBlock:(void(^)(id observer, NSDictionary *dictionary))actionBlock {
dispatch_semaphore_wait([CLActionManager sharedManager].semaphore, DISPATCH_TIME_FOREVER);
NSString *key = [NSString stringWithFormat:@"%@-%@",[NSString stringWithFormat:@"%p",observer], [[self keyWithActionType:type] stringByAppendingString:@"-1"]];
NSString *actionBlockKey = [key stringByAppendingString:@"-CLActionBlock-1"];
NSString *actionMainThreadKey = [key stringByAppendingString:@"-CLActionMainThread-1"];
NSMutableDictionary *blockDictionary = [[CLActionManager sharedManager].blockDictionaryMapTable objectForKey:observer];
if (!blockDictionary) {
blockDictionary = [NSMutableDictionary dictionary];
}
[blockDictionary setObject:actionBlock forKey:actionBlockKey];
NSMutableDictionary *mainThreadDictionary = [[CLActionManager sharedManager].mainThreadDictionaryMapTable objectForKey:observer];
if (!mainThreadDictionary) {
mainThreadDictionary = [NSMutableDictionary dictionary];
}
[mainThreadDictionary setObject:[NSNumber numberWithBool:mainThread] forKey:actionMainThreadKey];
[[CLActionManager sharedManager].observerMapTable setObject:observer forKey:key];
[[CLActionManager sharedManager].blockDictionaryMapTable setObject:blockDictionary forKey:observer];
[[CLActionManager sharedManager].mainThreadDictionaryMapTable setObject:mainThreadDictionary forKey:observer];
dispatch_semaphore_signal([CLActionManager sharedManager].semaphore);
}
2.发送通知
根据观察者和监听类型动态生成Key,然后找出对应观察者的属性,然后一一调用。
+ (void)postType:(CLActionType)type dictionary:(NSDictionary *)dictionary {
dispatch_semaphore_wait([CLActionManager sharedManager].semaphore, DISPATCH_TIME_FOREVER);
NSArray<NSString *> *keyArray = [[[CLActionManager sharedManager].observerMapTable keyEnumerator] allObjects];
NSString *identifier = [[self keyWithActionType:type] stringByAppendingString:@"-1"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF ENDSWITH %@",identifier];
NSArray<NSString *> *array = [keyArray filteredArrayUsingPredicate:predicate];
for (NSString *key in array) {
NSString *actionBlockKey = [key stringByAppendingString:@"-CLActionBlock-1"];
NSString *actionMainThreadKey = [key stringByAppendingString:@"-CLActionMainThread-1"];
id observer = [[CLActionManager sharedManager].observerMapTable objectForKey:key];
NSMutableDictionary *blockDictionary = [[CLActionManager sharedManager].blockDictionaryMapTable objectForKey:observer];
NSMutableDictionary *mainThreadDictionary = [[CLActionManager sharedManager].mainThreadDictionaryMapTable objectForKey:observer];
void(^block)(id observer, NSDictionary *dictionary) = [blockDictionary objectForKey:actionBlockKey];
BOOL mainThread = [[mainThreadDictionary objectForKey:actionMainThreadKey] boolValue];
if (block) {
if (mainThread) {
dispatch_async(dispatch_get_main_queue(), ^{
block(observer, dictionary);
});
}else {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
block(observer, dictionary);
});
}
}
}
dispatch_semaphore_signal([CLActionManager sharedManager].semaphore);
}
###3.使用协议的方式来分发事件
这里为了使用方便,采取了Block回调的方式来监听,但是我们也可以考虑协议分发的方式来实现,每个观察者遵守Action协议,发送通知的时候我们就可以找出对应观察者,调用协议中的方法来达到事件分发的目的。