(转载)链式文件生成器原理分析(二)

此文章由热心网友 ccSundayChina授权转载

接上篇文章,里面主要讲述了普通链式文件与分类链式文件的生成过程。今天讲一下里面的几个技术难点。

一:为什么要构造链式方法宏定义以及如何构造。

思考一个问题,当我们给类添加了链式方法后,比如- (MLChain4UIButton *(^)())addTarget_action_forControlEvents;其实我们就已经可以进行调用了,比如这样

UIButton.mlc_make. addTarget_action_forEvents(self, @selector(buttonClicked:), UIControlEventTouchUpInside)

这样写是不会报错的,但是因为我们没有给这个链式方法进行相应的实现,所以它是不会产生任何效果的。而如果亲自动手给每一个链式方法都添加实现的话,就偏离了我们的初衷。

仔细想一想,其实它的实现跟原生类里面的方法addTarget:(nullable id) action:(nonnull SEL) forControlEvents:(UIControlEvents)的实现是一模一样的,所以如果我们在调用链式方法的时候能够让这个链式对象所对应的原生对象去响应它的原生方法的话,那么就能实现我们想要的效果了,而这时候我们也不需要写任何的实现代码。

那么怎么实现这个功能呢,首先我们要知道的是,链式方法名与原生方法名是可以按照固定的规则进行相互的转换的,而链式对象与原生对象也是可以按照固定规则进行相互的转换。也就是说知道了一个就可以推算出另一个。这也是代码中NSObject+ChainInfoAdaptor分类所做的事情,它像是一个适配器一样,可以根据你的需要将链式方法名转换成原生方法名等,反之依然。而如果我们知道了原生的类,原生的方法,以及方法的参数的话,那么我们就可以根据NSInvocation逐一根据数据类型设置,最后使用[Invocation invoke]来手动的触发这个方法。

所以我们在调用链式方法的时候,希望可以将这个方法所对应的原生类、原生方法、以及它的各个参数都传递出去,让另一个专门的类(NSObject+ChainInvocation)对这些进行统一处理,最后实现原生对象调用原生方法的效果。

这时候仅仅通过一个链式方法是无法实现的,因此需要一个更巧妙的设计。

我们知道如果定义一个形如addTarget_action_forControlEvents(...)跟链式方法同名的的宏定义的话,那么我们再写上面的UIButton.mlc_make. addTarget_action_forEvents(self, @selector(buttonClicked:), UIControlEventTouchUpInside)的时候,程序就会进到那个宏里面去,这是我们必须清楚地一件事情。

知道了这个前提,我们就可以在这上面的宏中做文章了。思考一下如何使用这个宏传出去原生方法、方法的参数(个数不确定)和原生的类(这个可以在外面再获取),由于原生方法在生成链式方法的时候是已经知道了的,因为我们是通过原生方法映射出链式方法的,所以我们可以在自动生成文件的时候就将原生方法名作为宏中已知的默认第一项,而外界输入的可变参数作为第二个大项(可以通过RAC中的宏定义metamacro_at(N, ...)来依次获取参数的值)对外进行传递。所以我们需要的宏定义如下:

#define addTarget_action_forControlEvents(...)  addTarget_action_forControlEvents(@"addTarget:action:forControlEvents:", metamacro_at(0, __VA_ARGS__), metamacro_at(1, __VA_ARGS__), (long long)metamacro_at(2, __VA_ARGS__))                                  

因此我们在自动生成宏定义的时候围绕上面的原则进行生成就可以了,需要在注意一下参数的类型。关键代码如下:

macroDefineString = [NSString stringWithFormat: @"#ifndef %@\
 \n#define %@(...)  %@(@\"%@\", %@)\
 \n#endif", chainSelName, chainSelName, chainSelName, selName, [mulArgStrs componentsJoinedByString:@", "]];
//chainSelName:链式方法名,selName:原生方法名,mulArgStrs:该方法的参数类型数组。

二:如何在调用链式方法的时候,转为让原生的对象去响应原生的方法。

定义好了统一的链式宏定义后,我们希望能够将宏中的那些条件都传到一个固定的方法中进行处理。也就是说在调用链式方法的时候,无论是什么,它的实现都能够进到另一个固定的方法中。
为此我们在每一个类中的+(void) load;方法(这个方法的执行在main函数前)中给每一个链式方法都进行了动态的绑定,并将它们方法的实现转到一个固定的方法中。

+ (void)mlc_setUpMethodDynamically{
    Class originalClass = MLCOriginalClass(self);//获取链式类所对应的原生类
    Class chainBridgeClass = self;                            //当前链式类
    Method desMethod =  class_getInstanceMethod([originalClass class], @selector(mlc_rootChainMethod));      //mlc_rootChainMethod是我们统一处理所有链式方法的类,所有链式方法都会走这个方法。
    IMP imp = method_getImplementation(desMethod);//获取mlc_rootChainMethod的实现
    for (NSString *methodName in [originalClass mlc_noReturnValueSelNames]) {
        NSString *chainMethodName = [originalClass mlc_chainSelNameWithOringalSelName:methodName];//将原生方法名转换为链式方法名
        class_addMethod([chainBridgeClass class], sel_registerName(chainMethodName.UTF8String), imp, method_getTypeEncoding(desMethod));//给链式类动态添加链式方法,并且将mlc_rootChainMethod方法的实现作为它们的统一实现。
    }
}

下面看一下所有链式方法的最终实现:mlc_rootChainMethod方法。这是在NSObject+ChainInvocation分类中定义的方法。

- (instancetype (^)(NSString *selName, ...))mlc_rootChainMethod{
    return ^ id (NSString *selName, ...){
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
        id chainObject = [self performSelector:@selector(chainObject)];
#pragma clang diagnostic pop
        NSMethodSignature *sig = [chainObject methodSignatureForSelector:sel_registerName(selName.UTF8String)];
        NSInvocation *inv = [NSInvocation invocationWithMethodSignature:sig];
        [inv setTarget:chainObject];
        [inv setSelector:sel_registerName(selName.UTF8String)];
        va_list args;
        va_start(args, selName);
        [NSObject mlc_setInv:inv withSig:sig andArgs:args];
         va_end(args);
        [inv invoke];
        return self;
    };
    
}

在这里这个方法的block参数第一项就是原生类的原始方法,而后面接收一个可变参数的列表,也就是我们从宏定义中获取到的参数。
根据这些条件,我们就可以让原生类调用原生的方法了。

三:再梳理一下程序的流程

1、在生成文件的时候,让链式方法名与链式宏定义的名字一样。并在宏定义中将原生方法名作为第一项默认参数,输入的方法变量作为可变参数列表这个第二个大项。链式宏及方法如下:

#define chainSelName(...)  chainSelName(@"originalSelName", [mulArgStrs componentsJoinedByString:@", "]])
//chainSelName:链式方法名、originalSelName:原生方法、[mulArgStrs componentsJoinedByString:@", "]]方法参数类型宏定义。

- (MLChain4Object *(^)()) chainSelName;

2、程序启动时,给所有的链式类的链式方法做一个动态的绑定,并且将分类中的mlc_rootChainMethod方法作为他们的实现。

这样在书写形如UIButton.mlc_make. addTarget_action_forEvents(self, @selector(buttonClicked:), UIControlEventTouchUpInside)的时候,就会进到- (instancetype (^)(NSString *selName, ...))mlc_rootChainMethod方法中,在这里我们会获取到原生类、原生方法、参数列表,获取到这些我们就可以手动的触发方法了。主要就是使用NSInvocation逐一根据数据类型设置,设置的方法setInv:withSig:andArgs:从YYKit中得来。篇幅有限,这里就先不做过多的对这个方法的阐述了,之后会加入的。

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

推荐阅读更多精彩内容