本文为『移动前线』群在3月10日的分享总结整理而成,转载请注明来自『移动开发前线』公众号。
嘉宾介绍
蘑菇街李忠(花名银时,网名 limboy),多年客户端开发经验,目前主要负责移动端基础架构设计及核心技术难点攻克(以 iOS 为主),为集团所有 App 提供移动端解决方案。 热衷于尝试新技术,并在团队中推广,致力于以优秀的代码、新的理念拓宽工程师的思路和眼界,以提升团队整体作战能力为己任。
在组件化之前,蘑菇街 App 的代码都是在一个工程里开发的,在人比较少,
-
耦合比较严重(因为没有明确的约束,「组件」间引用的现象会比较多)
-
容易出现冲突(尤其是使用 Xib,还有就是 Xcode Project,虽说有脚本可以改善:https://github.com/truebit/xUnique )
-
业务方的开发效率不够高(只关心自己的组件,却要编译整个项目,与其他不相干的代码糅合在一起)
为了解决这些问题,就采取了「组件化」策略。它能带来这些好处:
-
加快编译速度(不用编译主客那一大坨代码了)
-
自由选择开发姿势(MVC / MVVM / FRP)
-
方便 QA 有针对性地测试
-
提高业务开发效率
先来看下,组件化之后的一个大概架构:
「组件化」顾名思义就是把一个大的 App 拆成一个个小的组件,相互之间不直接引用。那如何做呢?
实现方式
组件间通信
以 iOS 为例,由于之前就是采用的 URL 跳转模式,理论上页面之间的跳转只需 open 一个 URL 即可。所以对于一个组件来说,只要定义「支持哪些 URL」即可,比如详情页,大概可以这么做:
[MGJRouter registerURLPattern:@"mgj:// detail?id=:id" toHandler:^(NSDictionary *routerParameters) {
NSNumber *id = routerParameters[@"id"];
// create view controller with id
// push view controller}];
首页只需调用 [MGJRouter openURL:@"mgj://detail?id=404"
那问题又来了,我怎么知道有哪些可用的 URL?为此,我们做了一个后台专门来管理。
然后可以把这些短链生成不同平台所需的文件,iOS 平台生成 .{h,m} 文件,Android 平台生成 .java 文件,并注入到项目中。
目前还有一块没有做,就是参数这块,虽然描述了短链,
还有一种情况会稍微麻烦点,就是「组件A」要调用「组件B」
类似这种同步调用,iOS 之前采用了比较简单的方案,还是依托于 MGJRouter,
[MGJRouter registerURLPattern:@"mgj:// cart/ordercount" toObjectHandler:^id(NSDictiona ry *routerParamters){
// do some calculation
return @42;
}]
使用时 NSNumber *orderCount = [MGJRouter objectForURL:@"mgj://cart/
稍微复杂但更具通用性的方法是使用「协议」 <-> 「类」绑定的方式,还是以购物车为例,购物车组件可以提供这么个 Protocol
@protocol MGJCart <NSObject>
+ (NSInteger)orderCount;
@end
可以看到通过协议可以直接指定返回的数据类型。
那么,这个协议放在哪里比较合适呢?如果跟组件放在一起,
Android 也是采用类似的方式。
组件生命周期管理
理想中的组件可以很方便地集成到主客中,并且有跟 AppDel
先来看看现在的入口方法:
其中 [MGJApp startApp] 主要负责一些 SDK 的初始化。[self trackLaunchTime] 是我们打的一个点,
每个 Module 都实现了 ModuleProtocol,
还有一个问题就是,系统的一些事件会有通知,比如 applic
一个简单的解决方法是在 AppDelegate
壳工程
既然已经拆出去了,那拆出去的组件总得有个载体,
遇到的问题
组件拆分
由于之前的代码都是在一个工程下的,
假如要把详情页迁出来,就会发现它依赖了一些其他部分的代码,
版本管理
我们的组件包括第三方库都是通过 Cocoapods 来管理的,其中组件使用了私有库。之所以选择 Cocoapods,一个是因为它比较方便,
假如基础组件做了个 API 接口升级,这个升级会对原有的接口做改动,
然后我们就想了个办法,如果不在壳工程里指定基础库的版本,
还有一个问题是 pod update 时间过长,经常会在 Analyzing Dependency 上卡 10 多分钟,非常影响效率。后来排查下来是跟组件的 Podspec 有关,配置了 subspec,且依赖比较多。
然后就是 pod update 之后的编译,由于是源码编译,所以这块的时间花费也不少,
持续集成
在刚开始,持续集成还不是很完善,业务方升级组件,直接把 podspec 扔到 private repo 里就完事了。这样最简单,但也经常会带来编译通不过的问题。而且这种随意的版本升级也不太能保证质量。于是我们就搭建了一套持续集成系统,大概如此:
每个组件升级之前都需要先通过编译,然后再决定是否升级。
基于此,在经过了几轮讨论之后,有了新版的持续集成平台,
大致思路是,业务方如果要升级组件,假设现在的版本是 0.1.7,添加了一些 feature 之后,壳工程测试通过,想集成到主工程里看看效果,
当测试通过后,就可以把尾部的 -rc.n 去掉,然后点击「
周边设施
基础组件及组件的文档 / Demo / 单元测试
无线基础的职能是为集团提供解决方案,只是在蘑菇街 App 里能 work 是远远不够的,所以就需要提供入口,知道有哪些可用组件,
这就要求组件的负责人需要及时地更新 README / CHANGELOG / API,并且当发生 API 变更时,能够快速通知到使用方。
公共 UI 组件
组件化之后还有一个问题就是资源的重复性,
小结
「组件化」是 App 膨胀到一定体积后的解决方案,能一定程度上解决问题,
Q: 对协议部分也蛮好奇的,Android端有采用这种方法的可能性么?
A: Android我们有一个commanager, 思路类似,具体实现的话,需要再翻一下...
Q:不同组件间怎么监督和核查UI和代码资源的公用性,
A: 组件间的监督和核查机制,我们没有做,目前正在做的是’
Q: 每个组件所依赖的基础库是在各自的podfile中,
A: 在壳工程的podspec和主工程的podfile里都有,
Q:你的组件是如何响应URL的?
A: 组件会通过MGJRouter注册URL, 然后设置callback, 在callback里会通过NavigationControl
Q: 那这个NavigationController是不是要通过参
A: 不用,我们有一个UISkeletonModule模块,
Q: 蘑菇街目前的业务下,组件量有多少个?
A: iOS有70多个(包括基础的和业务的),Android更多。
Q: 那么多组件,开发的时候,是不是需要先熟悉70多个呢?
A: 这是个问题,组件分为基础组件和业务组件,
Q: 壳工程会将基础库都拷贝一份么,
A: 壳工程会通过pod去拉组件,
Q:所有的组件都是放在一个工程里么,
A: 组件都是有单独的工程的,然后会放到私有的pod源中进行管理。
Q: 你的组件在注册URL的时候已经实例化过了是么,
A: 在注册URL时实例化的,
Q: 为什么还需要一个壳工程,直接放在主工程下面不行么?
A: 壳工程用于“干净”的业务开发,直接放到主工程,
Q: 可以提及一下Model这块怎么处理的么,
A: Model一般不会在组件间传递,在组件内部倒是可以随便传,
Q: 组件间数据较多时怎样组件方式传递?
A: 目前还没有遇到这样的情况,如果比较多的话,
Q: 这些组件都是开源的么,做项目的开发者可以看到具体的实现么,
A: 对内是开放源码的,项目开发者可以看到,
Q: 一个新的需求有什么标准判断是新加一个组件来做还是在某个关联的
A: 其实没有太严格的标准,对于业务方来说,
Q: 请问一般组件化的发布周期是?需要业务手动发布吧?
A: 这个因组件而异,对于基础组件来说,没什么问题就不会去动它,
Q: 你们当初做组件化的过程中,
A: 有的,目前已经有几个是这么做了,
转自:微信公众号: [移动开发前线]
注:下周分享预告:分享主题:天猫App A/B测试实践经验