美文网首页
BlocksKit动态代理

BlocksKit动态代理

作者: 来鸿去燕 | 来源:发表于2017-12-23 23:36 被阅读0次

    AOP

    可以通过预编译方式和 运行期动态代理 实现在不修改源代码的情况下给程序动态统一添加功能的一种技术.

    用于:日志记录,性能统计,安全控制,事务处理,异常处理等等。

    将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

    添加属性

    在头文件中声明属性,也就是 xxxController 代理方法的对应 block 属性,以UIImageViewController为例。

    @property (nonatomic,copy) void(^bk_didFinishPickingMediaBlock)(UIImagePickerController *,NSDictionary *);
    @property (nonatomic,copy) void(^bk_didCancelBlock)(UIImagePickerController *);
    

    并且声明为动态生成这两个属性的存取方法。

    @dynamic bk_didFinishPickingMediaBlock;
    @dynamic bk_didCancelBlock;
    

    注册动态代理对象

    获取protocol对象

    在NSObject的block代理扩展:NSObject+A2BlockDelegate里,调用:a2_delegateProtocol(self)。传递self以及字符串@“Delegate”,拼装出如UIImagePickerControllerDelegate。根据名称获取Protocol对象。

    Protocol *a2_delegateProtocol(Class cls)
    {
        return a2_classProtocol(cls, @"Delegate", @"delegate");
    }
    

    通过传入具体的Class。用反射机制得到类的名称,再通过传入的Delegate拼接出改类所对应的代理类名称。例如UIImagePickerController所对应的代理为UIImagePickerControllerDelegate
    那么这里的_cls即为UIImagePickerControllersuffix@"Delegate"

    Protocol * objc_getProtocol ( const char *name ); 根据名字,返回指定的协议。
    如果仅仅是声明了一个协议,而未在任何类中实现这个协议,则该函数返回的是nil。

    开始注册

    这一步,会替换UIImagePickerController原来的setDelegate和delegate方法,并且用dynamicDelegate来替换原来的delegate对象。

    + (void)bk_registerDynamicDelegateNamed:(NSString *)delegateName forProtocol:(Protocol *)protocol
    {
        NSMapTable *propertyMap = [self bk_delegateInfoByProtocol:YES];
        A2BlockDelegateInfo *infoAsPtr = (__bridge void *)[propertyMap objectForKey:protocol];
        if (infoAsPtr != NULL) { return; }
    
        const char *name = delegateName.UTF8String;
        objc_property_t property = class_getProperty(self, name);
        SEL setter = setterForProperty(property, name);
        SEL a2_setter = prefixedSelector(setter);
        SEL getter = getterForProperty(property, name);
    
        A2BlockDelegateInfo info = {
            setter, a2_setter, getter
        };
    
        [propertyMap setObject:(__bridge id)&info forKey:protocol];
        infoAsPtr = (__bridge void *)[propertyMap objectForKey:protocol];
    
        IMP setterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject, id delegate) {
            A2DynamicDelegate *dynamicDelegate = getDynamicDelegate(delegatingObject, protocol, infoAsPtr, YES);
            if ([delegate isEqual:dynamicDelegate]) {
                delegate = nil;
            }
            dynamicDelegate.realDelegate = delegate;
        });
    
        if (!swizzleWithIMP(self, setter, a2_setter, setterImplementation, "v@:@", YES)) {
            bzero(infoAsPtr, sizeof(A2BlockDelegateInfo));
            return;
        }
    
        if (![self instancesRespondToSelector:getter]) {
            IMP getterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject) {
                return [delegatingObject bk_dynamicDelegateForProtocol:a2_protocolForDelegatingObject(delegatingObject, protocol)];
            });
    
            addMethodWithIMP(self, getter, NULL, getterImplementation, "@@:", NO);
        }
    }
    

    将protocol对象与delegate的setter和getter方法关联。

    // 创建一个映射关系容器。
    NSMapTable *propertyMap = [self bk_delegateInfoByProtocol:YES];
    A2BlockDelegateInfo *infoAsPtr = (__bridge void *)[propertyMap objectForKey:protocol];
    if (infoAsPtr != NULL) { return; }
    
    // 根据delegate这个property得到对应的setter和getter方法。
    const char *name = delegateName.UTF8String;
    objc_property_t property = class_getProperty(self, name);
    SEL setter = setterForProperty(property, name);
    SEL a2_setter = prefixedSelector(setter);
    SEL getter = getterForProperty(property, name);
    
    A2BlockDelegateInfo info = {
        setter, a2_setter, getter
    };
    
    [propertyMap setObject:(__bridge id)&info forKey:protocol];
    infoAsPtr = (__bridge void *)[propertyMap objectForKey:protocol];
        
    

    将原来delegate的setter方法替换为block实现。

    使用block实现delegate的setter方法。当有如下调用时:controller.delegate = self;就会触发这个函数指针。
    其中,delegatingObject为pickerController,delegate就是被代理的对象:xxController。

    IMP setterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject, id delegate) {
        A2DynamicDelegate *dynamicDelegate = getDynamicDelegate(delegatingObject, protocol, infoAsPtr, YES);
        if ([delegate isEqual:dynamicDelegate]) {
            delegate = nil;
        }
        dynamicDelegate.realDelegate = delegate;
    });
    
    if (!swizzleWithIMP(self, setter, a2_setter, setterImplementation, "v@:@", YES)) {
        bzero(infoAsPtr, sizeof(A2BlockDelegateInfo));
        return;
    }
    
    使用A2DynamicDelegate对象替换原来的delegate。

    通过UIImagePickerController及UIImagePickerControllerDelegate protocol,构造一个继承自NSProxy的dynamicDelegate。

    A2DynamicDelegate *dynamicDelegate = getDynamicDelegate(delegatingObject, protocol, infoAsPtr, YES);
    if ([delegate isEqual:dynamicDelegate]) {
        delegate = nil;
    }
    dynamicDelegate.realDelegate = delegate;
    
    • 1、构造:A2DynamicUIImagePickerControllerDelegate。继承自A2DynamicDelegate。并且实现:UIImagePickerControllerDelegate代理方法。
    • 2、得到A2DynamicUIImagePickerControllerDelegate代理对象,这个对象通过类关联的方式设置为UIImagePickerController的属性。dynamicDelegate
    • 3、前一步,通过消息转发获取controller的delegate,如果已经设置过了delegate为dynamicDelegate,那么直接返回。
    • 4、如果还没有设置,那么通过消息转发机制将UIImagePickerController的delegate设置为dynamicDelegate。(动态改变原来的代理对象为A2DynamicUIImagePickerControllerDelegate对象。这样后续的代理方法调用,都会走到该对象实现的UIImagePickerControllerDelegate代理方法中)。
    • 5、保存原来的真正delegate。这样,如果原来实现了UIImagePickerControllerDelegate的方法,则通过这个realDelegate继续调用原来的代理方法。
    static inline A2DynamicDelegate *getDynamicDelegate(NSObject *delegatingObject, Protocol *protocol, const A2BlockDelegateInfo *info, BOOL ensuring) {
        A2DynamicDelegate *dynamicDelegate = [delegatingObject bk_dynamicDelegateForProtocol:a2_protocolForDelegatingObject(delegatingObject, protocol)];
    
        if (!info || !info->setter || !info->getter) {
            return dynamicDelegate;
        }
    
        if (!info->a2_setter && !info->setter) { return dynamicDelegate; }
    
        id (*getterDispatch)(id, SEL) = (id (*)(id, SEL)) objc_msgSend;
        id originalDelegate = getterDispatch(delegatingObject, info->getter);
    
        if (bk_object_isKindOfClass(originalDelegate, A2DynamicDelegate.class)) { return dynamicDelegate; }
    
        void (*setterDispatch)(id, SEL, id) = (void (*)(id, SEL, id)) objc_msgSend;
        setterDispatch(delegatingObject, info->a2_setter ?: info->setter, dynamicDelegate);
    
        return dynamicDelegate;
    }
    

    将原来delegate的getter方法替换为block实现。

    这里会先判断有没有实现getter方法。

    if (![self instancesRespondToSelector:getter]) {
        IMP getterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject) {
            return [delegatingObject bk_dynamicDelegateForProtocol:a2_protocolForDelegatingObject(delegatingObject, protocol)];
        });
    
        addMethodWithIMP(self, getter, NULL, getterImplementation, "@@:", NO);
    }
    

    在dynamicDelegate中生成一个映射

    key为原始的代理方法的selector,value为bi_xxxx所传递的block参数。并且生成A2BlockInvocation来保存这个block。

    [self bk_linkDelegateMethods:@{ @"bk_didFinishPickingMediaBlock": @"imagePickerController:didFinishPickingMediaWithInfo:",
                                            @"bk_didCancelBlock": @"imagePickerControllerDidCancel:" }];
    

    dynamicDelegate中触发了原始的代理方法,那么根据原始方法的selector从dynamicDelegate的映射表中取出invocation。并且执行invocation保存的block。

    达到将代理方法的执行放到block中执行。

    + (void)bk_linkProtocol:(Protocol *)protocol methods:(NSDictionary *)dictionary
    {
        [dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *selectorName, BOOL *stop) {
            const char *name = propertyName.UTF8String;
            objc_property_t property = class_getProperty(self, name);
            NSCAssert(property, @"Property \"%@\" does not exist on class %s", propertyName, class_getName(self));
    
            char *dynamic = property_copyAttributeValue(property, "D");
            NSCAssert2(dynamic, @"Property \"%@\" on class %s must be backed with \"@dynamic\"", propertyName, class_getName(self));
            free(dynamic);
    
            char *copy = property_copyAttributeValue(property, "C");
            NSCAssert2(copy, @"Property \"%@\" on class %s must be defined with the \"copy\" attribute", propertyName, class_getName(self));
            free(copy);
    
            SEL selector = NSSelectorFromString(selectorName);
            SEL getter = getterForProperty(property, name);
            SEL setter = setterForProperty(property, name);
    
            if (class_respondsToSelector(self, setter) || class_respondsToSelector(self, getter)) { return; }
    
            const A2BlockDelegateInfo *info = [self bk_delegateInfoForProtocol:protocol];
    
            IMP getterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject) {
                A2DynamicDelegate *delegate = getDynamicDelegate(delegatingObject, protocol, info, NO);
                return [delegate blockImplementationForMethod:selector];
            });
    
            if (!class_addMethod(self, getter, getterImplementation, "@@:")) {
                NSCAssert(NO, @"Could not implement getter for \"%@\" property.", propertyName);
            }
    
            IMP setterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject, id block) {
                A2DynamicDelegate *delegate = getDynamicDelegate(delegatingObject, protocol, info, YES);
                [delegate implementMethod:selector withBlock:block];
            });
    
            if (!class_addMethod(self, setter, setterImplementation, "v@:@")) {
                NSCAssert(NO, @"Could not implement setter for \"%@\" property.", propertyName);
            }
        }];
    }
    

    创建一个对象->对象的映射关系

    NSDictionary 的局限性

    NSDictionary 提供了 key -> object 的映射。从本质上讲,NSDictionary 中存储的 object 位置是由 key 来索引的。

    由于对象存储在特定位置,NSDictionary 中要求 key 的值不能改变(否则 object 的位置会错误)。为了保证这一点,NSDictionary 会始终复制 key 到自己私有空间。

    这个 key 的复制行为也是 NSDictionary 如何工作的基础,但这也有一个限制:你只能使用 OC 对象作为 NSDictionary 的 key,并且必须支持 NSCopying 协议。此外,key 应该是小且高效的,以至于复制的时候不会对 CPU 和内存造成负担。

    这意味着,NSDictionary 中真的只适合将值类型的对象作为 key(如简短字符串和数字)。并不适合自己的模型类来做对象到对象的映射。

    NSMapTable(顾名思义)更适合于一般来说的映射概念。这取决于它的设计方式,NSMapTable 可以处理的 key -> obj 式映射如 NSDictionary,但它也可以处理 obj -> obj 的映射。

    其他

    下面这个函数用于构造一个protocol所持有的所有property。

    + (NSMapTable *)bk_delegateInfoByProtocol:(BOOL)createIfNeeded
    {
        NSMapTable *delegateInfo = objc_getAssociatedObject(self, _cmd);
        if (delegateInfo || !createIfNeeded) { return delegateInfo; }
    
        NSPointerFunctions *protocols = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsOpaqueMemory|NSPointerFunctionsObjectPointerPersonality];
        NSPointerFunctions *infoStruct = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsMallocMemory|NSPointerFunctionsStructPersonality|NSPointerFunctionsCopyIn];
        infoStruct.sizeFunction = A2BlockDelegateInfoSize;
        infoStruct.descriptionFunction = A2BlockDelegateInfoDescribe;
    
        delegateInfo = [[NSMapTable alloc] initWithKeyPointerFunctions:protocols valuePointerFunctions:infoStruct capacity:0];
        objc_setAssociatedObject(self, _cmd, delegateInfo, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
        return delegateInfo;
    }
    

    // 获取指定的属性
    objc_property_t class_getProperty ( Class cls, const char *name );

    下面获取属性的setter方法。

    static SEL setterForProperty(objc_property_t property, const char *name)
    {
        if (property) {
            char *setterName = property_copyAttributeValue(property, "S");
            if (setterName) {
                SEL setter = sel_getUid(setterName);
                free(setterName);
                if (setter) return setter;
            }
        }
    
        const char *propertyName = property ? property_getName(property) : name;
        return selectorWithPattern("set", propertyName, ":");
    }
    

    (A2BlockDelegateInfo) info = (setter = "setDelegate:", a2_setter = "a2_SetDelegate:", getter = "delegate")

    获取属性中指定的特性

    char * property_copyAttributeValue ( objc_property_t property, const char *attributeName );

    新加的属性必须是以@dynamic来修饰,不通过系统自动生成setter和getter方法。
    char *dynamic = property_copyAttributeValue(property, "D");

    相关文章

      网友评论

          本文标题:BlocksKit动态代理

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