美文网首页
基于协议分发器的 A/B Test 方案

基于协议分发器的 A/B Test 方案

作者: AppleTTT | 来源:发表于2017-10-26 14:35 被阅读110次

    最近在看一些博客的时候,发现了一篇 iOS A/B Test 方案探索 ,突然想到在上家公司(做电商的)的时候,接到了产品提出的 A/B Test 需求的时候,非常的恐惧,当时并没有什么好方案,也是用最粗劣的方式实现的,看了这篇博客后,感觉当时能够想到或者看到这样的方案的话,就不会有这么多的苦恼了。

    当时的苦恼

    大多数情况下,A/B 方案基本上是 UI 全变,不同的用户看到的内容不尽相同,当时我们组的处理办法就是在各种代理的方法里面添加 if 或者是 switch 语句,这样导致代码结构很混乱,本来只有 300 多行代码的控制器,一下子变成了 600 行,后面改成用两个控制器做选择,但是这样修改的地方就很多了,而且在控制器外面判断也导致了诸多的问题,并非可行的方案,下面我们就说下 基于协议分发器的 A/B Test 方案 是怎么实现的吧。

    可行发方案

    1. 抽离出 delegate 和 dataSource;即面对复杂的控制器,我们可以使用单独的类来实现这些代理方法。
    2. 协议分发;将协议与具体要实现的类结合起来,当需要此类去实现的时候,去指定具体的类去实现协议中的方法。
    3. 实现分发器的 “自释放” 。

    具体的实现

    抽离出 delegate 和 dataSource
    @interface DelegateSource : NSObject <UITableViewDataSource, UITableViewDelegate>
    
    @end
    

    单独创建一个类,来实现复杂的 UI 中的一些视图和逻辑;

    协议分发与 “自释放”
    @interface ProtocolDispatcher ()
    
    @property (nonatomic, strong) Protocol *prococol;
    @property (nonatomic, strong) NSArray *implemertors;
    
    @end
    
    @implementation ProtocolDispatcher
    
    + (id)dispatcherProtocol:(Protocol *)protocol toImplemertors:(NSArray *)implemertors {
        return [[ProtocolDispatcher alloc] initWithProtocol:protocol toImplemertors:implemertors];
    }
    
    - (instancetype)initWithProtocol:(Protocol *)protocol toImplemertors:(NSArray *)implemertors {
        if (self = [super init]) {
            self.prococol = protocol;
            NSMutableArray *implemertorContexts = [NSMutableArray arrayWithCapacity:implemertors.count];
            [implemertors enumerateObjectsUsingBlock:^(id implemertor, NSUInteger idx, BOOL * _Nonnull stop) {
                ImplemertorContext *implemertorContext = [ImplemertorContext new];
                implemertorContext.implemertor = implemertor;
                [implemertorContexts addObject:implemertorContext];
                // 由于分发器只是一个局部变量,将其放到给定的 implemertor 中,等 implemertor 释放是,分发器也会释放掉
                objc_setAssociatedObject(implemertor, _cmd, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            }];
            self.implemertors = implemertorContexts;
        }
        return self;
    }
    

    这里将协议和类绑定在一起,待外部传入协议和类之后,分给实现了协议的类去处理,那么怎么知道一个类是否以及实现了协议里面的方法呢,系统以及有了 protocol_getMethodDescription 函数来查看协议中是否有对应的方法,如下

    /**
     如何做到只对Protocol中Selector函数的调用做分发是设计的关键,系统提供有函数
     通过以下方法即可判断Selector是否属于某一Protocol
     
     objc_method_description 的两个成员变量分别表示 运行时方法的名字和方法的参数
     */
    struct objc_method_description MethodDescriptionForSELInProtocol(Protocol *protocol, SEL sel)
    {
        struct objc_method_description description = protocol_getMethodDescription(protocol, sel, YES, YES);
        if (description.types)
        {
            return description;
        }
        description = protocol_getMethodDescription(protocol, sel, NO, YES);
        if (description.types)
        {
            return description;
        }
        return (struct objc_method_description){NULL, NULL};
    }
    
    BOOL ProtocolContainSel(Protocol *protocol, SEL sel)
    {
        return MethodDescriptionForSELInProtocol(protocol, sel).types ? YES: NO;
    }
    

    另外,在 iOS 消息转发的动态特性中,我们可以实现一个类是否满足可以相应该方法。要注意重写 respondsToSelector 方法来判定是否可以相应此方法。

    // 一:动态解析
     + (BOOL)resolveInstanceMethod:(SEL)sel
     + (BOOL)resolveClassMethod:(SEL)sel
     
    // 二:快速转发
     // 返回实现了方法的消息转发对象
     - (id)forwardingTargetForSelector:(SEL)aSelector OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
     
    // 三:慢速转发
     // 函数签名
     - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
     //函数调用
     - (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");
    

    以上则是实现了 庞海礁 提到的协议分发器,此分发器可以将协议分发给多个实现者,如果函数有返回值,则前面返回的返回值会被后面返回的覆盖,即以数组最后一个可以实现该方法的类为准。但是我们需要实现的 A/B Test 是只需要有一个实现者即可,因此 李剑飞 在此基础上,修改了一下分发器的初始化方法

    /**
     协议分发器
     @param protocol 遵循的协议;
     @param indexImplemertor AB Test 需要执行的协议实现实例数组下标;
            若传入 对应的 NSNumber 数字, 则调用改实现实例的协议方法;
            若传入 nil,则调用全部的遵循协议的实现实例
     @param implemertors 所有需要遵循协议的实现实例;
     @return 协议分发器;
     */
    + (id)dispatcherProtocol:(Protocol *)protocol
        withIndexImplemertor:(NSNumber *)indexImplemertor
              toImplemertors:(NSArray *)implemertors;
    

    通过传入的 number 来决定具体由哪个 implemertor 去实现此协议,具体可以参考他的 github

     self.delegateSource_A = [UITableViewDelegateDataSource_A new];
        self.delegateSource_B = [UITableViewDelegateDataSource_B new];
        
        // A = 0
        // B = 1
        NSUInteger type = 1;
        
        self.tableView.delegate = ABTestProtocolDispatcher(UITableViewDelegate,
                                                       @(type),
                                                       self.delegateSource_A,
                                                       self.delegateSource_B);
        
        self.tableView.dataSource = ABTestProtocolDispatcher(UITableViewDataSource,
                                                         @(type),
                                                         self.delegateSource_A,
                                                         self.delegateSource_B);
    

    参考资料

    1. iOS A/B Test 方案探索
    2. Protocol 协议分发器
    3. iOS 释放自注销模式设计

    相关文章

      网友评论

          本文标题:基于协议分发器的 A/B Test 方案

          本文链接:https://www.haomeiwen.com/subject/fckkpxtx.html