iOS App组件化开发实践

iOS App组件化开发实践

 

曹俊_413f 关注

 0.3 2017.05.19 11:25* 字数 6071 阅读 1369评论 9喜欢 17

前因

其实我们这个7人iOS开发团队并不适合组件化开发。原因是因为性价比低,需要花很多时间和经历去做这件事,带来的收益并不能彻底改变什么。但是因为有2~3个星期的空档期,并不是很忙;另外是可以用在一个全新的App上。所以决定想尝试下组件化开发。

所谓尝试也就是说:去尝试解决组件化开发当中的一些问题。如果能解决,并且有比较好的解决方案,那就继续下去,否则就放弃。

背景

脱离实际情况去谈方案的选型是不合理的。

所以先简单介绍下背景:我们是一家纳斯达克交易所上市的科技企业。我们公司还有好几款App,由不同的几个团队去维护,我们是其中之一。我们这个团队是一个7人的iOS开发小团队。作者本人是小组长。

之前的App已经使用了模块化(CocoaPods)开发,并且已经使用了二进制化方案。App已经在使用自动化集成。

虽然要开发一个新App,但是很多业务和之前的App是一样的或者相似的。

为什么要写这篇博客?

想把整个过程记录下来,方便以后回顾。

我们的思路和解决方案不一定是对的或者是最好的。所以希望大家看了这篇博客之后,能给我们提供很多建议和别的解决方案,让我们可以优化使得这个组件化开发的方案能变得更加好。

技术栈

gitlab gitlab-runner CocoaPods CocoaPods-Packager fir 二进制化 fastlane deploymateoclint Kiwi

成果

使用组件化开发App之后:

代码提交更规范,质量提高。体现在测试人员反馈的bug明显减少。

编译加快。在都是源码的情况下:原App需要150s左右整个编译完毕,然后开发人员才可以开始调试。而现在组件化之后,某个业务组件只需要10s~20s左右。在依赖二进制化组件的情况下,业务组件编译速度一般低于10s。

分工更为明确,从而提升开发效率。

灵活,耦合低。

结合MVVM。非常细致的单元测试,提高代码质量,保证App稳定性。体现在测试人员反馈的bug明显减少。

回滚更方便。我们经常会发生业务或者UI变回之前版本的情况,以前我们都是checkout出之前的代码。而现在组件化了之后,我们只需要使用旧版本的业务组件Pod库,或者在旧版本的基础上再发一个Pod库。

新人更容易上手。

对于我来说:

更加容易地把控代码质量。

更加容易地知道小组成员做了些什么。

更加容易地分配工作。

更加容易地安排新成员。

解耦

我们的想法是这样的,就算最后做不成组件化开发,把这些应该重用的代码抽出来做成Pod库也没有什么影响。所以优先做了这一步。

哪些东西需要抽成Pod库?

我们之前的App已经使用了模块化(CocoaPods化)开发。我们已经把会在App之间重用的Util、Category、网络层和本地存储等等这些东西抽成了Pod库。还有一些和业务相关的,比如YTXChart,YTXChartSocket;这些也是在各个App之间重用的。

所以得出一个很简单的结论:要在App之间共享的代码就应该抽成Pod库,把它们作为一个个组件。

我们去仔细查看了原App代码,发现很多东西都需要重用而我们却没有把它们组件化。

为什么没有把这些代码组件化?

因为当时没想好怎么解耦,举个例子。

有一个类叫做YTXAnalytics。是依赖UMengAnalytics来做统计的。

它的耦合是在于一个方法。这个方法是用来收集信息的。它依赖了User,还依赖了currentServerId这个东西。

+ (NSDictionary*)collectEventInfo:(NSString*)event withData:(NSDictionary*)data{.......return@{@"event": event,@"eventType":@"event",@"time": [[[NSDatedate] timeIntervalSince1970InMillionSecond] stringValue],@"os": device.systemName,@"osVersion": device.systemVersion,@"device": device.model,@"screen": screenStr,@"network": [YTXAnalytics networkType],@"appVersion": [AppInfo appVersion],@"channel": [AppInfo marketId],@"deviceId": [ASIdentifierManager sharedManager].advertisingIdentifier.UUIDString,@"username": objectOrNull([YTXUserManager sharedManager].currentUser.username),@"userType": objectOrNull([[YTXUserManager sharedManager].currentUser.userType stringValue]),@"company": [[ServiceProvider sharedServiceProvider].currentServerId stringValue],@"ip": objectOrNull([SSNetworkInfo currentIPAddress]),@"data": jsonStr    };}

解决方案是,搞了一个block,把获取这些信息的责任丢出来。

[YTXAnalytics sharedAnalytics].analyticsDataBlock = ^NSDictionary*() {return@{@"appVersion": objectOrNull([PBBasicProviderModule appVersion]),@"channel": objectOrNull([PBBasicProviderModule marketId]),@"username": objectOrNull([PBUserManager shared].currentUser.username),@"userType": objectOrNull([PBUserManager shared].currentUser.userType),@"company": objectOrNull([PBUserManager shared].currentUser.serverId),@"ip": objectOrNull([SSNetworkInfo currentIPAddress])                };    };

我们的耦合大多数都是这种。解决方案都是弄了一个block,把获取信息的职责丢出来到外面。

我们解耦的方式就是以下几种:

1.把它依赖的代码先做成一个Pod库,然后转而依赖Pod库。有点像是“依赖下沉”。

2.使用category的方式把依赖改成组合的方式。

3.使用一个block或delegate(协议)把这部分职责丢出去。

4.直接copy代码。copy代码这个事情看起来很不优雅,但是它的好处就是快。对于一些不重要的工具方法,也可以直接copy到内部来用。

初始化

AppDelegate充斥着各种初始化。

比如我们自己的代码。已经只是截取了部分!

[selfsetupScreenShowManager];//event start[YTXAnalytics createYtxanalyticsTable];    [YTXAnalytics start];    [YTXAnalytics page:APP_OPEN];    [YTXAnalytics sharedAnalytics].analyticsDataBlock = ^NSDictionary*() {return@{@"appVersion": objectOrNull([AppInfo appVersion]),                .......@"ip": objectOrNull([SSNetworkInfo currentIPAddress]),                };    };        [selfregisterPreloadConfig];//Migrate UserDefault 转移standardUserDefault到group[NSUserDefaultsmigrateOldUserDefaultToGroup];    [ServiceProvider sharedServiceProvider];        [YTXChatManager sharedYTXChatManager];    [ChartSocketManager sharedChartSocketController].delegate = [ChartProvider sharedChartProvider];//初始化最初的行情集合[[ChartProvider sharedChartProvider] addMetalList:[ChartSocketManager sharedChartSocketController].quoteList];//初始化环信信息Manager[YTXEaseMobManager sharedManager];

比如第三方:

//注册环信[selfsetupEaseMob:application didFinishLaunchingWithOptions:launchOptions];//Talking Data[selfsetupTalkingData];    [selfsetupAdTalkingData];    [selfsetupShareSDK];    [selfsetupUmeng];    [selfsetupJSPatch];    [selfsetupAdhocSDK];    [YTXGdtAnalytics communicateWithGdt];//广点通

首先这些初始化的东西是会被各个业务组件都用到的。

那我组件化开发的时候,每一个业务组件如何保证我使用这些东西的时候已经初始化过了呢?难道每一个业务组件都初始化一遍?有参数怎么办,能不能使用单例?

但问题是第三方库基本都需要注册一个AppKey,我每一个业务组件里都写一份?那样肯定不好,那我配置在主App里的info.plist里面,每一个业务组件都初始化一下好了,也不会有什么副作用。但这样感觉不优雅,而且有很多重复代码。万一某个AppKey或重要参数改了,那每一个业务组件岂不是都得改了。这样肯定不行。另外一点,那我的业务组件必须依赖主App的内容了。无论是在主App里调试还是把主App的info.plist的相关内容拷贝过来使用。

更关键的是有一些第三方的库需要在application: didFinishLaunchingWithOptions:时初始化。

//初始化环信,shareSDK, 友盟, Talking Data等[selfsetupThirdParty:application didFinishLaunchingWithOptions:launchOptions];

有没有更好的办法呢?

首先我写了一个YTXModule。它利用runtime,不需要在AppDelegate中添加任何代码,就可以捕获App生命周期。

在某个想获得App生命周期的类中的.m中这样使用:

YTXMODULE_EXTERN(){//相当于loadisLoad =YES;}+ (BOOL)application:(UIApplication*)application willFinishLaunchingWithOptions:(nullableNSDictionary*)launchOptions{//实现一样的方法名,但是必须是静态方法。returnYES;}

分层

因为在解决初始化问题的时候,要先设计好层级结构。所以这里突然跳转到分层。

上个图:

layer

我们自己定了几个原则。

业务组件之间不能有依赖关系。

按照图示不能跨层依赖。

所谓弱业务组件就是包含着少部分业务,并且可以在这个App内的各个业务组件之间重用的代码。

要依赖YTXModule的组件一定要以Module结尾,而且它一定是个业务组件或是弱业务组件。

弱业务组件以App代号开头(比如PB),以Module结尾。例:PBBasicProviderModule。

业务组件以App代号开头(比如PB)BusinessModule结尾。例:PBHomePageBusinessModule。

业务组件之间不能有依赖关系,这是公认的的原则。否则就失去了组件化开发的核心价值。

弱业务组件之间也不应当有依赖关系。如果有依赖关系说明你的功能划分不准确。

初始化设计

我们约定好了层级结构,明确了职责之后。我们就可以跳回初始化的设计了。

创建一个PBBasicProviderModule弱业务组件。

它通过依赖YTXModule来捕捉App生命周期。

它来负责初始化自己的和第三方的东西。

所有业务组件都可以依赖这个弱业务组件。

它来保证所有东西一定是是初始化完毕的。

它来统一管理。

它来暴露一些类和功能给业务组件使用。

反正就是业务组件中依赖PBBasicProviderModule,它保证它里面的所有东西都是好用的。

因为有了PBBasicProviderModule,所以才让我更明确了弱业务组件这个概念。

因为我们懒,如果把PBBasicProvider定义为业务组件。那它和其他业务组件之间的通信就必须通过Bus、Notification或协议等等。

但它又肯定是业务啊。因为那些AppKey肯定是和这个App有关系的,也就是App的相关配置和参数也可以说是业务;我需要初始化设置那些Block依赖User信息、CurrentServerId等等肯定都是业务啊。

那只好搞个弱业务出来啊。因为我不能打破这个原则啊:业务组件之间不能互相依赖。

再进一步分清弱业务组件和业务组件。

业务组件里面基本都有:storyboard、nib、图片等等。弱业务组件里面一般没有。这不是绝对的,但一般情况是这样。

业务组件一般都是App上某一具体业务。比如首页、我、直播、行情详情、XX交易大盘、YY交易大盘、XX交易中盘、资讯、发现等等。而弱业务组件是给这些业务组件提供功能的,自己不直接表现在App上展示。

我们还可以创建一些弱业务组件给业务组件提供功能。当然了,不能够滥用。需要准确划分职责。

最后,代码大概是这样的:

@implementationPBBasicProviderModuleYTXMODULE_EXTERN(){    }+ (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(nullableNSDictionary*)launchOptions{    [selfsetupThirdParty:application didFinishLaunchingWithOptions:launchOptions];    [selfsetupBasic:application didFinishLaunchingWithOptions:launchOptions];returnYES;}+ (void) setupThirdParty:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions{dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0), ^{        [selfsetupEaseMob:application didFinishLaunchingWithOptions:launchOptions];        [selfsetupTalkingData];        [selfsetupAdTalkingData];        [selfsetupShareSDK];        [selfsetupJSPatch];        [selfsetupUmeng];//        [self setupAdhoc];});}+ (void) setupBasic:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions{    [selfregisterBasic];        [selfautoIncrementOpenAppCount];        [selfsetupScreenShowManager];        [selfsetupYTXAnalytics];        [selfsetupRemoteHook];}+ (YTXAnalytics) sharedYTXAnalytics{return......;}......

设想

这个PBBasicProviderModule简直就是个大杂烩啊,把很多以前写在AppDelegate里的东西都丢在里面了。毫无优雅可言。

的确是这样的,感觉没有更好的办法了。

既然已经这样了。我们可不可以大胆地设想一下:每个开发者开发自己负责的业务组件的时候不需要关心主App。

因为我知道美团的组件化开发必须依赖主App的AppDelegate的一大堆设置和初始化。所以干脆他们就直接在主App中集成调试,他们通过二进制化和去Pod依赖化的方式让主App的构建非常快。

所以我们是不是可以继续污染这个PBBasicProviderModule。不需要在主App项目里的AppDelegate写任何初始化代码?基本或者尽量不在主App里写任何代码?改依赖主App变为依赖这个弱业务组件?

按照这个思路我们搬空了AppDelegate里的所有代码。比如一些初始化App样式的东西、初始化RootViewController等等这些都可以搬到一个新的弱业务组件里。

而业务组件其实根本不需关心这个弱业务组件,开发人员只需要在业务组件中的Example App中的AppDelegate中初始化自己业务组件的RootViewController就好了。

其他的事情交给这个新的弱业务组件就好了。而主App和Example App只要在Podfile中依赖它就好了。

所以最后的设想就是:开发者不会去改主App项目,也不需要知道主App项目。对于开发者来说,主App和业务组件之间是隔绝的。

有一个更大的好处,我只要更换这个弱业务组件,这个业务组件就能马上适配一个新App。这也是某种意义上的解耦。

Debug/Release

谁说不用在主App里的AppDelegate写任何代码的,打脸。。。

我们在对二进制Pod库跑测试的发现,源码能过,二进制(.a)不能过。百思不得其解,然后仔细查看代码,发现是这个宏的锅:

#ifdef DEBUG#endif

DEBUG在编译阶段就已经决定了。二进制化的时候已经编译完成了。

而我们的代码中充满着#ifdef DEBUG 就这样这样。那怎么办,这是二进制化的锅。但是我们的二进制化已经形成了标准,大家都自觉会这么做,怎么解决这个问题呢。

解决方案是:

创建了一个PBEnvironmentProvider。大家都去依赖它。

然后原来判断宏的代码改成这样:

if([PBEnvironmentProvider testing]){//...}

在主App的AppDelegate中这样:

#if DEBUG && TESTING//PBEnvironmentProvider提供的宏CONFIG_ENVIRONMENT_TESTING#endif

原理是:如果AppDelegate有某个方法(CONFIG_ENVIRONMENT_TESTING宏会提供这个方法),[PBEnvironmentProvider testing]得到的结果就是YES。

为什么要写在主App里呢?其实也可以丢在PBBasicProviderModule里面,提供一个方法啊。

因为主App的AppDelegate.m是源码,未经编译。另外注意TESTING这个宏。我们可以在xcode设置里加一个macro参数TESTING,并且修改为0的情况下,能够生成一个实际是DEBUG的App但里面内容却是线上的内容。

这个需求是来自于我们经常需要紧急通过xcode直接build一个app到手机上以解决或确认线上的问题。

虽然打脸了,但是也还好,以后也不用改了。再说这个是特殊需求。除了这个之外,主App没有其他代码了。

业务组件间通信

我们解决了初始化和解耦的问题。接下来只要解决组件间通信的问题就好了。

然后我找了几个第三方库,选用了MGJRouter。本来直接依赖它就好了。

后来觉得都使用Block的方式会导致这样的代码,全部堆在了一个方法里:

+ (void) setupRouter{......[MGJRouter registerURLPattern:@"mgj://foo/a"toHandler:^(NSDictionary*routerParameters) {NSLog(@"routerParameterUserInfo:%@", routerParameters[MGJRouterParameterUserInfo]);}];[MGJRouter registerURLPattern:@"mgj://foo/b"toHandler:^(NSDictionary*routerParameters) {NSLog(@"routerParameterUserInfo:%@", routerParameters[MGJRouterParameterUserInfo]);}];......}

这样感觉很不爽。那我干脆就把MGJRouter代码复制了下来,把Block改成了@selector。并且把它直接加入了YTXModule里面。并且使用了宏,让结果看起来优雅些。代码看起来是这样的:

//在某个类的.m里,其实并不需要继承YTXModule也可以使用该功能YTXMODULE_EXTERN_ROUTER_OBJECT_METHOD(@"object1"){    YTXMODULE_EXAPAND_PARAMETERS(parameters)NSLog(@"%@ %@", userInfo, completion);    isCallRouterObjectMacro2 =YES;return@"我是个类型";}YTXMODULE_EXTERN_ROUTER_METHOD(@"YTX://QUERY/:query"){    YTXMODULE_EXAPAND_PARAMETERS(parameters)NSLog(@"%@ %@", userInfo, completion);    testQueryStringQueryValue = parameters[@"query"];;    testQueryStringNameValue = parameters[@"name"];    testQueryStringAgeValue = parameters[@"age"];}

调用的时候看起来是这样的:

[YTXModule openURL:@"YTX://QUERY/query?age=18&name=CJ"withUserInfo:@{@"Test":@1} completion:nil];NSString* testObject2 = [YTXModule objectForURL:@"object1"withUserInfo:@{@"Test":@2}];

通信问题解决了。其实页面跳转问题也解决了。

页面跳转

页面跳转解决方案与业务组件之间通信问题是一样的。

但是需要注意的是,你一个业务组件内部的页面跳转也请使用URL+Router的方式跳转,而不要自己直接pushViewController。

这样的好处是:如果将来某些内部跳转页面需要给其他业务组件调用,你就不需要再注册个URL了。因为本来就有。

是否去Model化

去Model化主要体现在业务组件间通信,要不要传一个Model过去(传过去的Dictionary中的某个键是Model)。

如果去Model化,这个业务组件的开发者如何确定Dictionary里面有哪些内容分别是什么类型呢?那需要有个地方传播这些信息,比如写在头文件,wiki等等。

如果不去Model化的话,就需要把这个Model做成Pod库。两个业务组件都去依赖它。

最后决定不去Model。因为实际上有一些Model就是在各个业务组件之间公用的(比如User),所以肯定就会有Model做成Pod库。我们可以把它做成重Model,Model里可以带网络请求和本地存储的方法。唯一不能避免的问题是,两个业务组件的开发者都有可能去改这个Model的Pod库。

信息的披露

跳转的页面需要传哪些参数?

业务组件之间传递数据时候本质的载体是什么?

不同业务开发者如何知晓这些信息。

使用去Model化和不使用去Model化,我们都有各自的方案。

去Model化,则披露头文件,在头文件里面写详细的注释。

如果不去Model化,则就看Model就可以了。如有特殊情况,那也是文档写在头文件内。

总结的话:信息披露的方式就是把注释文档写在头文件内。

组件的生命周期

业务组件的生命周期和App一样。它本身就是个类,只暴露类方法,不存在需要实例,所以其实不存在生命周期这个概念。而它可以使用类方法创建很多ViewController,ViewController的生命周期由App管理。哪怕这些ViewController之间需要通信,你也可以使用Bus/YTXModule/协议等等方式来做,而不应该让业务组件这个类来负责他们之间的通信;也不应该自己持有ViewController;这样增加了耦合。

弱业务组件的生命周期由创建它的对象来管理。按需创建和ARC自动释放。

基础功能组件和第三方的生命周期由创建它的对象来管理。按需创建和ARC自动释放。

版本规范

我们自己定的规则。

所有Pod库都只依赖到minor

"~> 2.3"

主App中精确依赖到patch

"2.3.1"

参考:Semantic Versioning RubyGems Versioning Policies

二进制化

二进制化我认为是必须的,能够加快开发速度。

而我使用的这个二进制方案

有个坑就是在gitlab-runner上在二进制和源码切换时,经常需要pod cache clean --all,test/lint/publish才能成功。而每次pod cache clean --all之后CocoaPods会去重新下载相关的pod库,增加了时间和不必要的开销。

我们现在通过podspec中增加preserve_paths和执行download_zip.sh解决了cache的问题。原理是让pod cache既有源码又有二进制.a。具体可以看ytx-pod-template项目中的Name.podspec和download_zip.sh。

二进制化还得注意宏的问题。小心使用宏,尤其是#ifdef。避免源码和二进制代码运行的结果不一样。

集成调试

集成调试很简单。每一个业务组件在自己的Example App中调试。

这个业务组件的podspec只要写清楚自己依赖的库有哪些。剩下的其他业务组件应该写在Example App的Podfile里面。

依赖的Pod库都是二进制的。如有问题可以装源码(IS_SOURCE=1 pod install)来调试。

开发人员其实只需要关心自己的业务组件,这个业务组件是自洽的。

公共库谁来维护的问题

这个问题在我们这种小Team不存在。没有仔细地去想过。但是只要做好代码准入(Test/Lint/Code Review)和权限管理就应该不会存在大的问题。

单元测试

单元测试我们用的是Kiwi。

结合MVVM模式,对每一个业务组件的ViewModel都进行单元测试。每次push代码,gitlab-runner都会自动跑测试。一旦开发人员发现测试挂了就能够及时找到问题。也可以很容易的追溯哪次提交把测试跑挂了。

这也是我们团队的强制要求。没有测试,测试写的不好,测试挂了,直接拒绝merge request。

gitlab-runner-test

lint

对每一个组件进行lint再发布,保证了正确性。这也是一步强制要求。

lint的时候能够发现很多问题。通常情况下不允许warning出现的。如果不能避免(比如第三方)请用--allow-warnings。

pod lib lint --sources=$SOURCES --verbose --fail-fast --use-libraries

统一的网络服务和本地存储方式

这个就很简单。把这两个部分抽象成几个Pod库供所有业务组件使用就好了。

我们这边分别是三个Pod库:

YTXRequest

YTXRestfulModel

NSUserDefault+YTX

其他一些内容

ignore了主App中的Podfile.lock尽量避免冲突。

主App Archive的时候要使用源码,而不是二进制。

后期可以使用oclint和deploymate检查代码。

使用fastlane match去维护开发证书。

一些需要从plist或者json读取配置的Pod库模块,要注意读出来的内容最好要加一个namespace。namespace可以是这个业务组件的名字。

业务组件读取资源文件的区别

#从main bundle中取。如果图片希望在storyboard中被找到,使用这种方式。s.resource = ["#{s.name}/Assets/**"]#只是希望在我这个业务组件的bundle内使用的plist。作为配置文件。这是官方推荐方式。s.resource_bundles = {"{s.name}/"=> ["{s.name}/Assets/config.plist"]}

持续集成

原来的App就是持续集成的。想当然的,我们希望新的组件化开发的App也能够持续集成。

Podfile应该是这样的:这里面出现的全是私有Pod库。

pod'YTXRequest','2.0.1'pod'YTXUtilCategory','1.6.0'pod'PBBasicProviderModule','0.2.1'pod'PBBasicChartAndSocketModule','0.3.1'pod'PBBasicAppInitModule','0.5.1'...pod'PBBasicHomepageBusinessModule','1.2.15'pod'PBBasicMeBusinessModule','1.2.10'pod'PBBasicLiveBusinessModule','1.2.1'pod'PBBasicChartBusinessModule','1.2.6'pod'PBBasicTradeBusinessModule','1.2.7'...

如果Pod依赖的东西特别特别多,比如100多个。另外又必须依赖主App做集成调试。

你也可以用这种方案:把你所有的Pod库的依赖都展开写到主App的Podfile中。而发布Pod库时podspec中不带任何的依赖的。这样就避免了pod install的时候解析依赖特别耗时的问题。

各个脚本都在这个ytx-pod-template。先从.gitlab-ci.yml看起。

我们持续集成的工具是gitlab runner。

持续集成的整个流程是:

第一步:

使用template创建Pod。像这样:

pod lib create --template-url="http://gitlab.baidao.com/pods/ytx-pod-template"

第二步:

创建dev分支。用来开发。

第三步:

每次push dev的时候会触发runner自动跑Stage: Init Lint(中的test)

gitlab-runner-init-test

第四步:

1.准备发布Pod库。修改podspec的版本号,打上相应tag。

2.使用merge_request.sh向master提交一个merge request。

gitlab-runner-merge-request

第五步:

1.其他有权限开发者code review之后,接受merge request。

2.master合并这个merge request

3.master触发runner自动跑Stage: Init Package Lint ReleasePod UpdateApp

第六步:

如果第五步正确。主App的dev分支会收到一个merge request,里面的内容是修改Podfile。

图中内容出现了AFNetworking等是因为这个时候在做测试。

gitlab-runner-merge-request-to-app

第七步:

主App触发runner,会构建一个ipa自动上传到fir。

Init

初始化一些环境。

打印一些信息。

Package

二进制化打包成.a

Lint

Pod lib lint。二进制和源码都lint。

测试。

以后考虑加入oclint和deploymate。

ReleasePod

把相关文件zip后,传到静态服务器库。以提供二进制化下载包。

pod repo push。发布该Pod库。

ReleasePod的时候不允许Pod库出现警告。

UpdateApp

下载App代码

修改Podfile文件。如果匹配到pod库文件名则修改,否则添加。

生成一个merge request到主App的dev分支。

关于gitlab runner。

stage这个功能非常的厉害。强烈推荐。

每一个stage可以跑在不同的runner上。每一个stage失败了可以单独retry。而某一个stage里面的任务可以并行执行:(test和lint就是并行的)

gitlab-runner-stage

小礼物走一走,来关注我

赞赏支持

 IOS

© 著作权归作者所有

举报文章

关注曹俊_413f

写了 34710 字,被 115 人关注,获得了 124 个喜欢

喜欢


17

更多分享

9条评论 只看作者

按时间倒序按时间正序


xi_lin

5楼 · 2018.06.08 18:29

你好,我想请教一下lint二进制的问题。目前lint二进制好像是用i386指令集的包来做的,但是分享的脚本里在生成二进制包的时候用`lipo -remove i386`把这个指令集删了,导致lint失败。请问你们现在的工作流程里怎么处理这个问题呢?自定义了pod lint吗?

赞  回复

曹俊_413f:

 @xi_lin 是因为我们所有的二进制库,相关依赖的,都没有i386,所以就会忽略

2018.08.24 11:50  回复

曹俊_413f:

 @xi_lin 或者用用看 --skip-impotrant-validation

2018.08.24 11:53  回复

 添加新评论


宿于松下

4楼 · 2018.01.07 22:51

组件化有4个大方向

1 组件通信机制

2 组件拆分

3 组件UT

4 组件开发、集成、发版

虽然这篇博文条理性不算特别清晰,但是基本每个点都有涉及,难能可贵,点赞。

赞  回复


yuanyi__

3楼 · 2017.12.08 17:44

你好,文章里面的YTXModule链接没了,方便的话,能重新添加下么。

赞  回复

曹俊_413f:

 @yuanyi__ 好

2017.12.08 17:46  回复

曹俊_413f:

 @yuanyi__ 好了

2017.12.08 17:54  回复

yuanyi__:

 @曹俊_413f 非常感谢

2017.12.11 21:33  回复

 添加新评论


丶纳凉

2楼 · 2017.06.27 14:28

挺好的.

赞  回复

你可能感兴趣的:(iOS App组件化开发实践)