iOS奇思妙想之使用block替代通知

前言

iOS開發中,不少狀況下會使用到通知,通知的好處不少,可是也有不少坑點,一旦沒有管理好,就會形成不少莫名其妙的bug。既然通知使用不當很容易出現問題,那有沒有什麼辦法來避免?通過思考後,決定使用block回調的方式來實現通知,而且避免掉通知的弊端。html

原理

參考通知原理,採用單例全局管理,單例持有一個字典,字典中儲存全部添加的block,在調用block的時候從字典中取出對應的block調用。通知原理參考--->>深刻理解iOS NSNotification ios

1.完整的單例建立

單例建立須要考慮到各類初始化方法以及拷貝,還有線程安全。具體參考--->>完整單例模式寫法git

2.保證觀察者生命週期不受單例影響

由於是單例持有的字典,就會形成block得不到釋放,從而引發一系列問題。這裏採用NSMapTable來儲存block,NSMapTable使用強引用key,弱引用value,這樣作的好處在於,當其中儲存的對象銷燬後,會自動從NSMapTable移除。使用NSMapTable能夠保證生命週期不受單例影響。具體參考--->>Cocoa 集合類型:NSPointerArray,NSMapTable,NSHashTablegithub

3.觀察者和block綁定

爲了使用簡單,而且保證block生命週期和觀察者同樣,使用RunTime動態綁定,將block和觀察者綁定起來。具體參考--->>iOS Runtime詳解數組

4.保證一個對象只添加一次觀察者

屢次添加觀察者會形成調用的時候響應屢次,這裏採用對象內存地址和標識符做爲字典的key,保證一個對象只添加一次。安全

5.多線程安全

這裏採用GCD信號量來保證線程安全,具體參考--->>GCD信號量bash

6.block循環引用

對於block循環引用,這裏採用回調觀察者替代self,保證不會循環引用,具體參考--->>Block循環引用詳解多線程

代碼

上面講解的就是整個項目實現的關鍵點,這裏貼出具體代碼。async

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

複製代碼

測試結果

效果圖

測試效果已經達到咱們須要的效果,看一下打印結果,全部的觀察者的生命週期都沒有受到影響ide

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

相關文章
相關標籤/搜索