iOS奇思妙想之使用block替代通知(一)
前言
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