前言
iOS開發中,很多情況下會使用到通知,通知的好處很多,但是也有很多坑點,一旦沒有管理好,就會造成很多莫名其妙的bug,既然通知使用不當很容易出現問題,那有沒有什么辦法來避免?經過思考后,決定使用block回呼的方式來實作通知,并且避免掉通知的弊端,
作為一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流群:196800191,加群密碼:112233,不管你是小白還是大牛歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 大家一起交流學習成長!
原理
參考通知原理,采用單例全域管理,單例持有一個字典,字典中儲存所有添加的block,在呼叫block的時候從字典中取出對應的block呼叫,通知原理參考—>>深入理解iOS NSNotification
1.完整的單例創建
單例創建需要考慮到各種初始化方法以及拷貝,還有執行緒安全,具體參考—>>完整單例模式寫法
2.保證觀察者生命周期不受單例影響
因為是單例持有的字典,就會造成block得不到釋放,從而引起一系列問題,這里采用NSMapTable來儲存block,NSMapTable使用強參考key,弱參考value,這樣做的好處在于,當其中儲存的物件銷毀后,會自動從NSMapTable移除,使用NSMapTable可以保證生命周期不受單例影響,具體參考—>>Cocoa 集合型別:NSPointerArray,NSMapTable,NSHashTable
3.觀察者和block系結
為了使用簡單,并且保證block生命周期和觀察者一樣,使用RunTime動態系結,將block和觀察者系結起來,具體參考—>>iOS Runtime詳解
4.保證一個物件只添加一次觀察者
多次添加觀察者會造成呼叫的時候回應多次,這里采用物件記憶體地址和識別符號作為字典的key,保證一個物件只添加一次,
5.多執行緒安全
這里采用GCD信號量來保證執行緒安全,具體參考—>>GCD信號量
6.block回圈參考
對于block回圈參考,這里采用回呼觀察者替代self,保證不會回圈參考,具體參考—>>Block回圈參考詳解
代碼
上面講解的就是整個專案實作的關鍵點,這里貼出具體代碼,
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實作代碼
//
// CLActionManager.m
// CLActionManager
//
// Created by AUG on 2018/8/12.
// Copyright ? 2018年 JmoVxia. All rights reserved.
//
#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
測驗結果

測驗效果已經達到我們需要的效果,看一下列印結果,所有的觀察者的生命周期都沒有受到影響、
AViewController收到顏色變化,當前執行緒<NSThread: 0x608000263000>{number = 1, name = main}
BViewController收到顏色變化,當前執行緒<NSThread: 0x608000263000>{number = 1, name = main}
ViewController收到顏色變化,當前執行緒<NSThread: 0x608000263000>{number = 1, name = main}
收到其他地方頭像變化了,當前執行緒--<NSThread: 0x608000263000>{number = 1, name = main}
收到其他地方頭像變化了,當前執行緒--<NSThread: 0x608000263000>{number = 1, name = main}
++++++++++>>>>BViewController銷毀了
頭像View銷毀了----0x7fbee1d08fb0
--------->>>>AViewController銷毀了
頭像View銷毀了----0x7fbee1e1a3b0
總結
以上是根據通知原理來自己實作的自定義回應類,希望能夠給大家幫助,demo地址—>>CLActionManager
原文作者:季末微夏
原文地址:https://www.jianshu.com/p/00c558c72ae7
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/233139.html
標籤:其他
