BlocksKit动态代理

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");

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容

  • 禅与 Objective-C 编程艺术 (Zen and the Art of the Objective-C C...
    GrayLand阅读 1,566评论 1 10
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 【2017年最新】☞ iOS面试题及答案 设计模式是什么? 你知道哪些设计模式,并简要叙述? 设计模式是一种编码经...
    紫色冰雨阅读 563评论 0 1
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,036评论 29 470
  • 雪花飞 李奇峰 漫天洒作云舞,清风迤逦传呼。不到白头不与休,梨花满皇都。 小园梅煮酒,清香出玉壶。英雄执杯论...
    简德萌阅读 179评论 0 0