0%

iOS模块化

项目背景

随着业务的增长,代码量和人员越来越多的情况下,公司iOS项目已经很长时间没做系统化的整理和重构,比较严重的问题表现在:

  1. 耦合严重:各业务线代码之间相互依赖,调用关系错综复杂,难于维护;
  2. 代码复用率低:随着人员的迁移,版本的迭代,开发同学会随意愿添加很多带个人色彩的代码,造了很多重复的轮子;
  3. 工程编译速度慢:代码归一到单一项目中管理,验证功能或bug fixed只能编译运行整个项目;
    基于此,想通过模块化思想来缓解这些问题。

    项目目标

  4. 提高开发效率和编译速度;
  5. 解除业务耦合,减轻代码冲突,降低维护成本;
  6. 提高代码复用率;

项目方案

  1. 各业务线代码独立为模块,模块以静态Framework工程形态存在;
  2. 各模块可以单独开发,编译,运行,测试,其他模块以Framework方式引入,以此提高编译速度和开发效率;
  3. 制定模块间通讯协议,以此解开业务线之间的耦合;
  4. 不具有业务属性的功能代码独立为组件,以CocoaPods方式引入,以此提高代码复用;

实施

整体架构图

整体架构图

通讯协议

我Coding了一套基于Protocol和URLRouter的通讯协议,命名为Tangram(七巧板)
模块间通讯

Protocol

这种通讯方式是为方便本地调用,保留了编译时类型检查和错误提示等,下面贴一些关键代码。

三个核心函数

1
2
3
4
5
6
// 注册协议
[[Tangram shareInstance] registerService:@protocol(BizModuleProtocol) service:[self class]];
// 调用协议实例
[[Tangram shareInstance] createService:@protocol(BizModuleProtocol)];
// 调用协议类实例
[[Tangram shareInstance] serviceClass:@protocol(BizModuleProtocol)];

定义Protocol

1
2
3
@protocol BizModuleProtocol
+ (NSString *)sayHello;
@end

实现Protocol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 帮助宏,快速绑定protocol和实现类
#define EXPORT_SERVICE(protocol) \
+ (void)load { [[Tangram shareInstance] registerService:protocol service:[self class]]; } \
+ (BOOL)singleton { return NO;}

@interface BizModuleProtocolImpl () <BizModuleProtocol>

@end

@implementation BizModuleProtocolImpl

EXPORT_SERVICE(@protocol(BizModuleProtocol));

+ (NSString *)sayHello {
return @"hello";
}

@end

调用Protocol方法

1
2
3
#define CreateService(proto) (id<proto>)[[Tangram shareInstance] createService:@protocol(proto)]

NSLog(@"%@", [CreateService(BizModuleProtocol) sayHello]);

URLRouter

这种通讯方式是用于APP内页面跳转的场景,并方便外部协议(推送、唤醒、H5、RN etc.)对接这些场景,网上一堆的开源代码,我就不细说了,下面贴一些关键代码。

注册

1
2
3
4
5
6
7
8
9
10
11
12
[Tangram registerURLPattern:kURLPatternModuleViewController toHandler:^(NSDictionary *routerParameters) {
NSString *url = routerParameters[URLRouterParameterURL];
URLRounterCompletionHandle handler = routerParameters[URLRouterParameterCompletion];
// 参数
NSDictionary *params = routerParameters[URLRouterParameterUserInfo];
// 跳转
UIViewController *viewController = [self routerTo:params];
// 回调
if(handler && viewController) {
handler(viewController);
}
}];

调用

1
2
3
4
NSDictionary *params = @{@"key1" : @"value1", @"key2" : @"value2"};
[Tangram openURL:kURLPatternModuleViewController withUserInfo:params completion:^(id result) {
NSLog(@"open url finish:%@", result);
}];

两种通讯方式说明

  1. 是根据使用场景定出了这两种通讯方式;
  2. Protocol方式面向内部,有利于编译时发现问题并修正,而直接调用Protocol定义的方法也不改变原有的开发习惯,但使用上会比较繁琐;
  3. URLRouter方式面向外部,用好了可无缝对接APP内任何一页面,但参数只允许传递字符串导致失去编译时查错能力同时使用上也不像调用方法来得方便,另外也会涉及一些安全问题,可以做一些白名单之类的保护;
  4. 两种通讯方式并存的模式在公司iOS项目中已经使用了一年半多,运作还算正常;

模块&组件管理

模块

  1. 模块的划分以业务和强业务相关的基础服务为维度进行划分,例如业务:首页、个人中心、订单、商品详情等;例如基础服务:埋点、分享、唤醒等;
  2. 模块以静态Framework工程形态存在,Xcode工程结构大概长下图样子;
    xcode工程结构
  3. 需要注意的地方是:没有完成解除依赖前,各模块之间的依赖关系还是非常严重(表现在:头文件、枚举、常量依赖等),还有包体的大小限制,导致模块的Framework需要设置为静态库,BuildSetting做相应修改;
    1
    2
    MACH_O_TYPE = staticlib;
    HEADER_SEARCH_PATHS = "<依赖到的头文件引目录路径>"

组件

  1. 这里说的的组件都是不包含任何的业务逻辑的功能代码,分为自有的和第三方的依赖库,通过CocoaPods引入;
  2. 自有组件因为改动会很频繁,所以通过DevelopmentPod管理;
  3. 自有组件划分建议:视图库、帮助库、日志库、公共模型库等;

规范

最后是模块化过程中涉及到的一些规范

设计模式

统一使用MVVM架构,老的MVC架构在业务迭代过程中慢慢转化(或者永远不会动)。

编码规范

Objective-C编码规范:bhttp://google.github.io/styleguide/objcguide.html

APP框架规范

  1. 新增UIViewController必须继承BaseViewController;
  2. 新增UITableViewController必须继承BaseTableViewController;
  3. 新增UICollectionController必须继承BaseCollectionController

目录&工程规范

  1. 模块目录和主工程目录同级,Xcode工程结构需要跟硬盘目录结构保持一致,文件按名字排序(有利于避免合并代码冲突);
  2. 模块下的文件固定是Xcode工程文件+Classes目录;
  3. Classes下按功能划分目录,Shared放置所有功能共享代码和其他工程配置文件;
  4. 功能目录下对应MVC、MVVM功能代码,帮助类代码和资源;
    建议APP目录结构

基础组件调用规范

这里不详说。

第三方SDK接入规范

这里不详说。

并行开发代码冲突协调规范

出现代码冲突,必须要找发生冲突的相关人讨论怎样合并,如果解决不了可拉上架构组同学一起Review解决。

参考

URLRouter: https://github.com/lightory/HHRouter
阿里的模块化开发框架:https://github.com/alibaba/BeeHive