UIViewController解耦尝试

当我们使用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"
}

其中Test1Test2被称为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主页

    原文作者:hikui
    原文地址: https://segmentfault.com/a/1190000000618118
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞