欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2022-03-22 10:41:40
前言iOS开发中,很多情况下会使用到通知,通知的好处很多,但是也有很多坑点,一旦没有管理好,就会造成很多莫名其妙的bug。既然通知使用不当很容易出现问题,那有没有什么办法来避免?经过思考后,决定使用block回调的方式来实现通知,并且避免掉通知的弊端。原理参考通知原理,采用单例全局管理,单例持有一个字典,字典中储存所有添加的block,在调用block的时候从字典中取出对应的block调用。通知原理参考—>>深入理解iOS NSNotification1.完整的单例创建单例创建需要考虑...

前言

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

测试结果
iOS奇思妙想之使用block替代通知(一)
测试效果已经达到我们需要的效果,看一下打印结果,所有的观察者的生命周期都没有受到影响、

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://blog.csdn.net/iOSxiaodaidai/article/details/110962311