(转载)链式文件生成器原理分析(一)
此文章由热心网友 ccSundayChina授权转载
在OC里面实现链式编程,可以使用返回调用者自身来实现。但是类有很多,每个类也有很多方法,假如要实现链式编程,则需要每一个方法进行命令与实现,工作量之大可想而知。
事实是虽然工作量巨大,但是却充满了吸引力。
之前见过有人将UIKit
和Foundation
框架中的大部分类一个个的都通过手工的方式添加上了链式编程的功能。洋洋洒洒的写了很多,不得不佩服这种愚公精神,然而仔细看代码的话会发现其实有很多一样的地方,而这些一样的地方是可以提炼出来的。
懒惰是一个程序员的美德。
今天要来介绍一个非常有意思的框架,这个框架会帮助我们自动的生成那些链式文件,不需要我们有多勤奋,挨个的去写实现,只需要我们传入我们想要进行转换的类名数组就可以了。
首先老规矩,框架地址。
关于怎么使用可以具体看作者的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文件流程图.png下面来看一下普通链式头文件(
.h文件
)是如何生成的,还是看流程图:
首先判断该类是不是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中查看。可以说这是一个设计非常巧妙的架构,可以学到很多的东西,笔者也会持续的对它进行分析并绘制相关的程序流程图,希望能帮助小伙伴理解,当然有错误的话还望指正出来。