iOS组件化及多项目开发解决方案

组件化开发是大家经常讨论的话题,也是随着项目的迭代、工程会变得越来越臃肿而想到的解决方案,其实目前网上组件化方案非常多,每一种都有自己的优缺点,我个人觉得没有最好的方案,只有最适合自己项目的方案。

在这里给大家介绍下最近我在公司用的一套组件化解决方案:项目中组件通过protocol协议依赖,达到组件间的解耦合的效果,组件不需要知道其它组件具体实现类,即使其它组件不存在也可以正常编译运行,业务组件可快速集成到其它项目中,同时可通过配置文件解决多个项目中存在差异化的问题。

下图是项目的架构层级图:

项目架构层级图

这里主要介绍下基础组件层中三个组件的具体实现:

  • YPLaunchManager 用于管理各个业务模块在 UIApplicationDelegate 事件中的任务,隔离业务与App Delegate之间的耦合;

  • YPProtocolMediator Protocol-ClassName 组件化实现方案的中间件,用于各个业务模块之间的通信;

  • YPAppModuleConfigManager 管理各个业务module的配置,主要解决一个module用于不同项目中时且存在差异的问题。

1,YPLaunchManager 用于对AppDelegate与业务功能/模块解耦

顾名思义是一个启动任务管理器,iOS 项目启动相关的事件都在AppDelegate.m 文件里面,因此YPLaunchManager是为了解决 AppDelegate 过于复杂且易耦合其它业务模块的问题。

YPLaunchManager 用于管理YPLaunchTask任务,每个业务功能/模块需继承自YPLaunchTask,并且实现相应的协议方法,通过 load 方案进行自动注册,还可以通过finishAfter 来控制业务功能/模块的生命周期,

@interface YPXXXXXLaunchTask : YPLaunchTask
@end

......

// load 方法里面增加 YPLaunchTaskInitNotification 初始化注册的通知,在通知里面调用 +add 来注册self
+ (void)load {
    [[NSNotificationCenter defaultCenter] addObserverForName:YPLaunchTaskInitNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
        [[self class] add];
    }];
}

// 通过 priority 来控制每个任务的优先级调用
- (NSUInteger)priority {
    return YPCoreSessionPriority;
}

// 通过finishAfterRun控制Task的生命周期
- (BOOL)finishAfterRun {
    return NO;
}

// 实现 YPLaunchTaskDelegate,与 UIApplicationDelegate 代理方法一致
- (void)runAppDidFinishLauchingBeforeUI {
    // 初始化网络相关的数据
}

- (void)runAppDidFinishLauchingAfterUI {
}

最后AppDelegate 里面的代码如下:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // 发送 YPLaunchTaskInitNotification 通知,注册所有的LaunchTask
    [[YPLaunchManager defautlManager] loadAllLaunchTask];
    [[YPLaunchManager defautlManager] runAppDidFinishLauchingBeforeUI];

    //这里需要 设置 self.window.rootViewController = xxx;

    [[YPLaunchManager defautlManager] runAppDidFinishLauchingAfterUI];

    return YES;
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    [[YPLaunchManager defautlManager] runAppDidEnterBackground];
}

附YPLaunchManager的时序图:

YPLaunchManager的时序图
2,YPProtocolMediator 是一种 Protocol-ClassName 组件化方案的中间件

YPProtocolMediator 通过Protocol与Modlue 的 ClassName匹配进行查找到具体实现的组件类,然后组件类实现的Protocol协议方法来实现通信的一种机制。

每个组件如果需要外界主动调用则需要实现port协议,如果需要向外界传递数据则应实现delegate协议,以下是 YPProtocolMediator 的框架图:

YPProtocolMediator
  • XXX_Port 定义其它模块调用的接口协议

  • XXX_Delegate 定义其它模块接收数据的接口协议;

以下是A与B模块的通信的实例:

// ModuleA 从ModuleB 获取数据

// 方式一,通过 port_excuteSelector 方法通信,即使@selector 为声明编译也不会报错
 NSString *uid = [YPMediatorPort(YPUserCenterPort) port_excuteSelector:@selector(port_Uid)];

// selector 带参数时用下面的方法,注意的是args 传入的是可变参数,参数不能为nil
[YPMediatorPort(YPUserCenterPort) port_excuteSelector:@selector(port_Login:password:) args:PhoneNumber, YPParamVaule(Value), nil];

// 方式二:通过获取 ModuleB 的实例,直接调用方法
NSNumber *userId = [YPMediatorPort(YPUserCenterPort) port_Uid];

[YPMediatorPort(ModuleXXXB_Port) port_Login:@"phoneNumber" password:@"password"];

方式一 和 方式二的区别是,如果 ModuleXXXB_Port 更新且不兼容旧版,删除了某个方法。

方式一可以编译通过,但是会报警告;

方式二编译不通过,直接报错;

所以当 ModuleA 强依赖 ModuleB 里面的某个方法时,则建议使用方式二通信,其它情况则建议方式一进行通信。

ModuleA 接收 ModuleB 的数据传递:

// 方法一:通过 YPProtocolMediator 组件实现的给类对象动态添加函数的方法(-addSelector:forProtocol:block:bindTarget:)进行通信 
// 向 ModuleA 类添加方法通过 block 回调来实现数据传递
[self yp_addDelegateSelector:@selector(userCenter:loginStatus:) paramBlock:^(YPParams *params) {
    id targetB = params[0];
    NSNumber *loginStatus = [params objectAtIndex:1];
    NSLog(@"target %@ %@, status is %@",targetB, loginStatus);
} forProtocol:@protocol(YPUserCenterDelegate)];

// 方法二:可以像传统的delegate来实现数据传递
// 1,将self 与 ModuleXXXB_Delegate 代理绑定
YPMediatorBind(self, ModuleXXXB_Delegate);
// 2,ModuleA 实现 ModuleXXXB_Delegate 的代理方法
- (void)userCenter:(id<ModuleXXXB_Port>)moduleXXXB loginStatus:(BOOL)loginStatus {
}

YPProtocolMediator 组件查找实现:

诚如上面说的 ,YPProtocolMediator 查找Module 是通过Protocol 与 ClassName 匹配进行查找的。

实现代码如下:

/**根据传入的组件协议返回实现该协议的类的对象*/
- (id)moduleInstanceFromProtocol:(Protocol *)protocol {
    // 获取协议名字
    NSString *className = NSStringFromProtocol(protocol);

    //......此处省略代码
    if ([className hasSuffix:self.portSuffix]) {
        className = [className substringToIndex:className.length - self.portSuffix.length];
    }
    // 通过协议名字来查找对应class
    Class aClass = NSClassFromString(className);

    //......此处省略代码
    // 没有找到对应的module,是否设置了 defaultModule 来处理相关事件
    if (!aClass && self.defaultModule) {
        aClass = NSClassFromString(self.defaultModule);
    }
    // 生成 module 实例
    module = [[aClass alloc] init];
    if ([module conformsToProtocol:protocol]){
        self.modulesDictionary[NSStringFromProtocol(protocol)] = module;
        return module;
    }

    // 未找到实现该协议的组件
    return nil;
}

3,YPAppModuleConfigManager 是各个业务组件的配置管理类,主要解决业务组件用于不同项目中又存在差异的问题

YPAppModuleConfigManager 通过加载json配置文件数据,然后 Module 通过 YPAppModuleConfigManager 获取自己模块的ModuleConfig

设计框架图如下:

YPAppModuleConfigManager

实际使用:

// 可结合 YPLaunchManager 使用
- (void)runAppDidFinishLauchingBeforeUI {
    // 加载 SystemConfig
    NSString *systemConfigPath = [[NSBundle mainBundle] pathForResource:@"SystemConfig" ofType:@"json"];
    [YPConfigManager loadModuleConfigWithPath:ModuleConfigPath];
    // 加载 Module 的config
    NSString *ModuleConfigPath = [[NSBundle mainBundle] pathForResource:@"ModuleAConfig" ofType:@"json"];
    [YPConfigManager loadModuleConfigWithPath:systemConfigPath];
}

------ moduleA 获取配置
YPModuleA_ModuleConfig *configModel = [[YPAppModuleConfigManager sharedManager] parseModuleConfigClass:[YPModuleA_ModuleConfig class]];
// 设置相应的颜色和字体 等等
self.navigation.color = configModel.navigationBarTintColor
self.navigation.titleFont = configModel.navigationTitleFont

json 文件示例:
{
    "YPBaseControllerModuleConfig" : {
        "navigationBarTintColor":"#ffffff", // navigation 颜色
        "navigationTitleFont":"Medium 18.0f", // navigation 字体
        "backNavigationItemImageName" : "yp_navigation_back", // navigation 返回图片
    },
}

最后附项目Demo地址

参考资料:iOS组件化方案对比

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 宿命 夜未眠 是谁的颂唱 月未缺 是谁的留恋 问天静 不忍将清风扰乱 向世安 只欲把庭界看穿...
    陈汐年阅读 460评论 4 8
  • 邻居王叔年轻时经营一家汽配店,招了几个学徒,生意红红火火,大约在10年前,就成为我们当地有钱的生意人,开豪车,买了...
    苍穹之下小人儿阅读 1,074评论 1 6
  • 今年新接手的班级,班里有几个孩子不太上趟儿,总是不习惯交家庭作业。哼!换了新老师,还想偷懒不交作业来?这群毛孩子,...
    笑盈子阅读 827评论 4 8
  • 二、成功的人,是自律的普通人 1.悔恨录:只长年龄,不长见识的人 人的工作能力和社会能力,真的不会和年龄一起增长,...
    庄颖琦阅读 1,109评论 0 0