链式文件生成器原理分析(一)

在OC里面实现链式编程,可以使用返回调用者自身来实现。但是类有很多,每个类也有很多方法,假如要实现链式编程,则需要每一个方法进行命令与实现,工作量之大可想而知。

事实是虽然工作量巨大,但是却充满了吸引力。

之前见过有人将UIKitFoundation框架中的大部分类一个个的都通过手工的方式添加上了链式编程的功能。洋洋洒洒的写了很多,不得不佩服这种愚公精神,然而仔细看代码的话会发现其实有很多一样的地方,而这些一样的地方是可以提炼出来的。

懒惰是一个程序员的美德。

今天要来介绍一个非常有意思的框架,这个框架会帮助我们自动的生成那些链式文件,不需要我们有多勤奋,挨个的去写实现,只需要我们传入我们想要进行转换的类名数组就可以了。

首先老规矩,框架地址

关于怎么使用可以具体看作者的readme,里面讲的比较详细了,然而就像作者其他的作品一样,代码里面注释依然是少得可怜。笔者也是断断续续的看了两个星期,才对这个代码的大致流程梳理的比较清晰了。

下面是笔者整理的程序流程图,水平有限,不准确之处还望指出。

part1-整体流程图.png

流程图中可以看出,当我们输入我们的类数组后,首先会进到一个循环里去,如果这个类是OC类的话那么就会被加入到新的数组中,并且也会将它的父类加入到新数组中,总之第一步就是对要处理的类进行处理。

然后输出一个NSMutableSet,我们要生成的链式类,就是这里面的。

紧接着就是开始根据类生成链式文件了,并写到了mac的桌面上。并且生成的文件主要是两类,一类是MLChainObject类型的,另一类是Object+MLChain类型的。这两类的作用不同,但又是互相紧密联系的。

由于代码量巨大,逻辑复杂,所以在一章篇幅里是很难将它解释的明白的。(也可以解释,但可能会丢掉好多有营养的东西)。今天我们主要看看在mac桌面生成普通链式文件的大致过程。

还是先看下笔者画的流程图。(强烈建议原作者做一点代码的解释工作。。。)

生成普通的链式文件.png

可以看到我们将处理后的类名数组传进去,就会以类名作为条件生成对应的头文件(.h)、实现文件(.m)、桥梁类名、以及父类名。然后根据这些条件创建我们专门用来生成代码文件的模型CodeModel,最后通过NSFileManager文件管理工具,将文件的代码字符串写到mac桌面文件中。

        [[NSFileManager defaultManager] writefileString:model.hFileResultString ToFileWithDiretory:XcodeCreateCodeDirectory fileName:chainClassName fileType:kML_CreateCodeFileType_h moveToTrashWhenFileExists:YES];
        
        [[NSFileManager defaultManager] writefileString:model.mFileResultString ToFileWithDiretory:XcodeCreateCodeDirectory fileName:chainClassName fileType:kML_CreateCodeFileType_m moveToTrashWhenFileExists:YES];

下面来看一下普通链式头文件(.h文件)是如何生成的,还是看流程图:

生成普通.h文件流程图.png

首先判断该类是不是NSObject基类,然后对应生成不同的属性,属性的目的是为了获取当前链式对象所绑定的的原生对象。同时运用runtime获取传入类的所有方法,并对方法进行过滤,主要就是去掉私有方法、转换set、get方法、过滤链式方法等,然后开始循环遍历方法数组中的每一个方法,运用适配器将方法都转换成链式方法,其实主要就是加了一个前缀,用以区别。其中比较难理解的就是生成链式宏定义(ChainMacroDefines)的过程,生成了链式的宏定义,然后再将该宏定义与诸如(MLChainClass*(^)())chainSelName的链式方法进行拼接。比如这样:

#ifndef numberOfLines                                  
#define numberOfLines(...)  numberOfLines(@"setNumberOfLines:", (long long)metamacro_at(0, __VA_ARGS__))                                  
#endif
/**     ClassName-> UILabel                                
SEL:   setNumberOfLines: 'q'
     */
- (MLChain4UILabel *(^)())numberOfLines;

按照此种方式对该类所有的方法都进行处理,并加入到methodAndMacro数组中,等遍历完该类中的所有的方法时,就将该数组中的所有元素进行拼接,返回该类所有符合条件方法的宏定义与对应的链式方法名的拼接字符串。

到这里.h文件的主要内容基本就完成了。然后就是再让它拼接属性字符串,以构造完整的content。

总之.h文件里生成了该类及其父类方法所对应的链式方法,并定义了用来接收可变参数的宏定义。宏定义的名字跟链式方法名一样,这样我们在使用点语法调用链式方法的时候,实际上就会进入到宏中,而在这个宏中会接收一个可变的参数列表。如下面代码所指示的:

#ifndef addTarget_action_forControlEvents                                  
#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__))                                  
#endif
/**     ClassName-> UIButton                                
SEL:   addTarget: '@'
  action: ':'
  forControlEvents: 'Q'
     */
- (MLChain4UIButton *(^)())addTarget_action_forControlEvents;

当调用obj. mlc_make.addTarget_action_forControlEvents(self,SEL action, UIControlEventTouchUpInside)的时候,其实最终会进入NSObject+ChainInvocation.h分类中的方法- (instancetype (^)(NSString *selName, ...))mlc_rootChainMethod,在这里我们会获取到链式对象所绑定的原生对象与传入的可变参数,最后转换为原生对象调用原生的方法。(更加详细的解析会在下一篇文章中)

如何生成文件,那就是事先你需要将该文件的具体内容,按照规则,拼接成一个具体的字符串,然后在通过NSFileManager写到桌面就可以了。

既然链式类中调用所有的方法最终都会转换为原生类调用原生的方法,所以在链式类的实现文件中,就没有必要写具体的实现了。这就是这个框架设计的巧妙之处。

看了普通链式头文件中的内容,下面简单的看一下普通链式实现文件中的代码:

/**
 m文件内容
 
 @param className <#className description#>
 @return <#return value description#>
 */
+ (NSString *)mlc_mFileContentStrWithClassName:(NSString *)className{
    NSMutableArray *resultStrs = [[NSMutableArray alloc] init];
 
    if ([className isEqualToString:@"NSObject"]) {
        NSString *  mfileContentString = @"+ (void)load{\n\
        \n  [self mlc_setUpMethodDynamically];\
        \n}";
         [resultStrs addObject:mfileContentString];
    }else{
      NSString *  mfileContentString = @"+ (void)load{\n\
        \n  [self mlc_setUpMethodDynamically];\
        \n}";
        NSString *chainObjectMethod =
        [NSString stringWithFormat:
         @"- (%@ *)chainObject{\
         \n    return (id)[super chainObject];\
         \n}", className];
        [resultStrs addObject:mfileContentString];
        [resultStrs addObject:chainObjectMethod];
    }
    return [resultStrs componentsJoinedByString:@"\n"];
    
}

实现文件的代码比较简单,简单讲就是添加了两个方法,一个是+ (void)load方法,另一个是获取该链式类所绑定的原生类的get方法。
load方法会在main函数前就调用,给链式类中的所有方法做一个动态添加方法的操作,并且以- (instancetype (^)(NSString *selName, ...))mlc_rootChainMethod作为它们的方法实现,也就是说当调用链式方法的时候,就会进到- (instancetype (^)(NSString *selName, ...))mlc_rootChainMethod中,而在这个方法里,我们会让链式对象所绑定的原生对象调用链式方法所对应的原生方法。

关于OBJ+MLChain分类文件的生成

分类里面的代码是比较少的,也是比较容易理解的。主要就是为了当调用obj.mlc_make的时候能够返回一个桥梁对象,同时也是为了模仿Masonry的设置方式,作者在这里添加了类方法与实例方法。(这里就不做过多的解释了。)
头文件代码如下:

+ (NSString *)mlc_methodStringInCategory
{
    NSMutableString *resultString = [[NSMutableString alloc] init];
    NSString *methodString1 = [NSString stringWithFormat:@"+ (MLChain4%@ *)mlc_make;\n\n", NSStringFromClass(self)];
    NSString *methodString2 = [NSString stringWithFormat:@"- (MLChain4%@ *)mlc_make;\n\n", NSStringFromClass(self)];
    NSString *methodString3 = [NSString stringWithFormat:@"+ (MLChain4%@ *)mlc_makeConfigs:(void(^)(MLChain4%@ *maker))block;\n\n", NSStringFromClass(self), NSStringFromClass(self)];
    NSString *methodString4 = [NSString stringWithFormat:@"- (MLChain4%@ *)mlc_makeConfigs:(void(^)(MLChain4%@ *maker))block;\n\n", NSStringFromClass(self), NSStringFromClass(self)];
    [resultString appendString:methodString1];
    [resultString appendString:methodString2];
    [resultString appendString:methodString3];
    [resultString appendString:methodString4];
    return resultString;
    
}

以上所讲的就是普通链式文件与分类文件的大致生成过程,并简要的讲了下代码是如何进行调用的。下篇文章会对这个框架的几个难点进行分析,一个是方法宏定义的生成、另一个是链式方法的调用过程,其中关于调用过程里作者用到了YYKit中的一个重要的方法,下篇文章也会进行阐述。

最后建议大伙自己去demo中查看,可以说这是一个设计非常巧妙的架构,可以学到很多的东西(喜欢的记得给作者star哈)。笔者也会持续的对它进行分析并绘制相关的程序流程图,希望能帮助小伙伴理解,当然有错误的话还望指正出来。

另外一篇关于这个框架的原理分析已经写好了,这里是传送门

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

推荐阅读更多精彩内容