当我们使用UIViewController时,从一个ViewController跳到另外一个ViewController,最简单的代码(不用storyboard的情况下)就是alloc一个实例,然后用navigation controller去push它。比如
#import "ViewController2.h"
//.....
- (void)buttonPushOnTouch:(id)sender {
ViewController2 *vc2 = [[ViewController2 alloc]init]];
[self.navigationViewController pushViewController:vc2 animate:YES];
}
而这样一来,这个ViewController必须知道另外一个ViewController的存在,而且需要import它的头文件。这样一来,两个ViewController就紧密耦合了。
在一般的程序里面,这么做没有任何问题。但是我们公司的两个主打产品有一个特殊需求就是:两个产品共用部分界面,而这些共用界面中的一些按钮,点击之后,在不同的程序里需要push出不同的界面(界面初始化需要的参数是一样的,但就是完全不同的界面)。而这两个产品又是由不同的小组去完成的,他们不共享同一个工程。
这时,我们想到的是将这些共用界面提取出来做成framework。那么问题就出现了:我如果在framework里面的view controller新建一个下一级view controller,就需要知道下一级view controller的头文件,下一级view controller又实际存在于每一个主工程里面。这样就出现了循环依赖的情况,这样做出来的framework即使能编译通过也没法维护。
好在ObjC是一个动态语言,实际上我们根本不需要知道一个类的信息就能初始化它。使用的方法就是NSClassFromString()
函数,此时你只要知道class名字就行,如果它返回一个Class
不为nil
,就可以实例化它,并且用KVC注入需要的property。所以稍加改进的代码如下:
NSString *viewControllerClassName = IN_PROJ_1?@"AAA":@"BBB";
Class aViewControllerClass = NSClassFromString(viewControllerClassName);
if(aViewControllerClass != nil){
id anInstance = [[aViewControllerClass alloc]init];
[anInstance setValue:xxx forKeyPath:@"xxx"];
if (anInstance != nil){
[self.navigationController pushViewController:anInstance animate:YES];
}
}
虽然这个能解耦,但是每次写那么冗余的代码总归不易维护。所以我尝试把这些东西抽象出来,以配置文件的形式,在不同工程里配置不同的界面。灵感来自于前几年在写Java的时候用的Spring。最后做了一个叫做EMViewControllerManager的东西。
EMControllerManager的配置文件的基本结构如下:
{
"Test1":{
"ClassName":"Test1ViewController",
"Description":"The homepage of the app",
"Tag":"100",
"Dependencies":{
"dependentString":"@Yes, you can inject a string using the config file",
"dependentInt":1000,
"dependentBool":true,
"test2ViewController":"Test2"
}
},
"Test2":"Test2ViewController"
}
其中Test1
、Test2
被称为view controller名称(昵称)。ClassName
指定了view controller的真实类名,而Dependencies
可以做一点基础的DI功能。
然后在AppDelegate
里面载入配置文件:
EMControllerManager *cm = [EMControllerManager sharedInstance];
NSString *path = [[NSBundle mainBundle]pathForResource:@"ViewControllerConfig" ofType:@"json"];
NSError *e = nil;
[cm loadConfigFileOfPath:path fileType:EMControllerManagerConfigFileTypeJSON error:&e];
if (e) {
NSLog(@"%@",[e localizedDescription]);
}
而在view controller里面,可以这么来初始化一个view controller对象:
UIViewController *vc = [cm createViewControllerInstanceNamed:@"Test1" withPropertyValues:@{@"color":[UIColor redColor],@"number":@(1)}];
if(vc != nil){
[self.navigationController pushViewController:vc animate:YES]
}
其中Named:
是view controller的昵称,withPropertyValues:
是初始化之后马上注入的property。
不同的工程只要改配置文件,而不需要改具体的代码,就能达到差异化的效果。
具体用法请阅读EMControllerManager的Github主页