iOS Block一对多回调的实现

前言

我们都知道,回调的方案有Block、代理、通知。要想实现一对多就要用通知。而且很方便的在多个地方进行回调,实现我们任意的方法。
然而是否考虑过用Block去实现一对多的回调。注意:不是无聊的想象哦,可以让我们看到另一面。

原理

一般情况下,Block的回调确实是一对一的。要想实现一对多,我们能想到什么?缓存block,循环调用。没错,本质上是这样。现在我们需要考虑一下几个问题:

1、用什么缓存来保证不会循环引用?
2、Block的一对一?
3、如何遍历调用?
4、还有不想用的Block如何快捷移除?

下面我们来一一解决以上问题。

实现方案

1、第一个问题:
首先我们可以考虑使用 NSMapTable 来缓存Block。因为这种缓存方式可以设置KEY和Value的缓存策略,并且可以把KEY设置成Object。设置Value的缓存策略,可以让我们把绑定的Block的Target也就是我们Value设置成 NSPointerFunctionsWeakMemory 这样就可以在Target释放的时候,自动移除我们的此条数据。

2、第二个问题
我们可以使用 runtime 的关联对象,关联当前监听对象 observer 和Block。

3、第三个问题
遍历的时候,遍历所有的存储的 observer,找出其相对应的KEY,再用KEY找到所关联的Block。从而实现回调。

4、第四个问题
其实第四个问题,在第一个问题的基础上也已经得到了解决。但是我们有可能在一个类里,也就是同一个Observer中有多个回调,此时我们需要的KeyTable中的数据无法移除。此时单独Remove即可

具体实现如下:

@interface Manger ()

@property (nonatomic, strong) NSMapTable *valueTable;

@property (nonatomic, strong) NSMapTable *keyTable;

@end
@implementation Manger

+ (instancetype)sharedInstance
{
    static Manger *gInteractor = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (!gInteractor) {
            gInteractor = [Manger new];
            gInteractor.valueTable = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableWeakMemory];
            gInteractor.keyTable = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableWeakMemory];
        }
    });
    return gInteractor;
}

- (void)clickSelector:(id)obsever callBack:(void(^)(NSString *callStr))callBack {
    
    // 使用内存地址保证一个对象置监听一次
    NSString *key = [NSString stringWithFormat:@"%@-key", [NSString stringWithFormat:@"%p", obsever]];
    NSString *keyValue = [key stringByAppendingString:@"-KeyBlock"];
    
    [self.valueTable setObject:obsever forKey:key];
    [self.keyTable setObject:keyValue forKey:key];
    
    // keyValue 和 block 进行关联
    objc_setAssociatedObject(obsever, CFBridgingRetain(keyValue), callBack, OBJC_ASSOCIATION_COPY);
}

- (void)clickSelector {
    
    NSArray *keyArr = [[self.valueTable keyEnumerator] allObjects];
    
    [keyArr enumerateObjectsUsingBlock:^(NSString * _Nonnull key, NSUInteger idx, BOOL * _Nonnull stop) {
        
        id observe = [self.valueTable objectForKey:key];
        NSString *valueKey = [self.keyTable objectForKey:key];
        void(^block)(NSString *callStr) = objc_getAssociatedObject(observe, (__bridge const void * _Nonnull)(valueKey));
        
        if (block) {
            block([NSString stringWithFormat:@"%@", NSStringFromClass([self class])]);
        }
    }];
    
    NSLog(@"%@\n%@", self.valueTable, self.keyTable);
}

/// 移除的时候,仅仅只是移除的KeyTable数据,因为ValueTable中的数据,随着Observer的释放而自动移除了
- (void)remove:(id)obj {
    
    NSString *key = [NSString stringWithFormat:@"%@-key", [NSString stringWithFormat:@"%p", obj]];
    [self.keyTable removeObjectForKey:key];
}

@end

你可能感兴趣的:(iOS Block一对多回调的实现)