VIA:infoQ 架構

前因
其實我們這個7人iOS開發團隊并不適合組件化開發。原因是因為性價比低,需要花很多時間和經歷去做這件事,帶來的收益并不能徹底改變什么。但是因為有2~3個星期的空檔期,并不是很忙;另外是可以用在一個全新的App上。所以決定想嘗試下組件化開發。
所謂嘗試也就是說:去嘗試解決組件化開發當中的一些問題。如果能解決,并且有比較好的解決方案,那就繼續下去,否則就放棄。
背景
脫離實際情況去談方案的選型是不合理的。

所以先簡單介紹下背景:我們是一家納斯達克交易所上市的科技企業。我們公司還有好幾款App,由不同的幾個團隊去維護,我們是其中之一。我們這個團隊是一個7人的iOS開發小團隊。作者本人是小組長。
之前的App已經使用了模塊化(CocoaPods)開發,并且已經使用了二進制化方案。App已經在使用自動化集成。
雖然要開發一個新App,但是很多業務和之前的App是一樣的或者相似的。
為什么要寫這篇博客?

想把整個過程記錄下來,方便以后回顧。
我們的思路和解決方案不一定是對的或者是最好的。所以希望大家看了這篇博客之后,能給我們提供很多建議和別的解決方案,讓我們可以優化使得這個組件化開發的方案能變得更加好。
技術棧
gitlab
gitlab-runner
CocoaPods
CocoaPods-Packager
fir
二進制化
fastlane
deploymate
oclint
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" : [[[NSDate date] 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,把獲取信息的職責丟出來到外面。
    我們解耦的方式就是以下幾種:
    把它依賴的代碼先做成一個Pod庫,然后轉而依賴Pod庫。有點像是“依賴下沉”。

使用category的方式把依賴改成組合的方式。

使用一個block或delegate(協議)把這部分職責丟出去。

直接copy代碼。copy代碼這個事情看起來很不優雅,但是它的好處就是快。對于一些不重要的工具方法,也可以直接copy到內部來用。

初始化
AppDelegate充斥著各種初始化。 比如我們自己的代碼。已經只是截取了部分!
[self setupScreenShowManager]; //event start [YTXAnalytics createYtxanalyticsTable]; [YTXAnalytics start]; [YTXAnalytics page:APP_OPEN]; [YTXAnalytics sharedAnalytics].analyticsDataBlock = ^ NSDictionary *() { return @{ @"appVersion" : objectOrNull([AppInfo appVersion]), ....... @"ip" : objectOrNull([SSNetworkInfo currentIPAddress]), }; }; [self registerPreloadConfig]; //Migrate UserDefault 轉移standardUserDefault到group [NSUserDefaults migrateOldUserDefaultToGroup]; [ServiceProvider sharedServiceProvider]; [YTXChatManager sharedYTXChatManager]; [ChartSocketManager sharedChartSocketController].delegate = [ChartProvider sharedChartProvider]; //初始化最初的行情集合 [[ChartProvider sharedChartProvider] addMetalList:[ChartSocketManager sharedChartSocketController].quoteList]; //初始化環信信息Manager [YTXEaseMobManager sharedManager];
比如第三方:
//注冊環信 [self setupEaseMob:application didFinishLaunchingWithOptions:launchOptions]; //Talking Data [self setupTalkingData]; [self setupAdTalkingData]; [self setupShareSDK]; [self setupUmeng]; [self setupJSPatch]; [self setupAdhocSDK]; [YTXGdtAnalytics communicateWithGdt];//廣點通
首先這些初始化的東西是會被各個業務組件都用到的。
那我組件化開發的時候,每一個業務組件如何保證我使用這些東西的時候已經初始化過了呢?難道每一個業務組件都初始化一遍?有參數怎么辦,能不能使用單例?
但問題是第三方庫基本都需要注冊一個AppKey,我每一個業務組件里都寫一份?那樣肯定不好,那我配置在主App里的info.plist里面,每一個業務組件都初始化一下好了,也不會有什么副作用。但這樣感覺不優雅,而且有很多重復代碼。萬一某個AppKey或重要參數改了,那每一個業務組件豈不是都得改了。這樣肯定不行。另外一點,那我的業務組件必須依賴主App的內容了。無論是在主App里調試還是把主App的info.plist的相關內容拷貝過來使用。
更關鍵的是有一些第三方的庫需要在application: didFinishLaunchingWithOptions:時初始化。
//初始化環信,shareSDK, 友盟, Talking Data等[self setupThirdParty:application didFinishLaunchingWithOptions:launchOptions];
有沒有更好的辦法呢?
首先我寫了一個YTXModule。它利用runtime,不需要在AppDelegate中添加任何代碼,就可以捕獲App生命周期。
在某個想獲得App生命周期的類中的.m中這樣使用:
YTXMODULE_EXTERN(){ //相當于load isLoad = YES;}+ (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(nullable NSDictionary *)launchOptions{ //實現一樣的方法名,但是必須是靜態方法。 return YES;}
分層
因為在解決初始化問題的時候,要先設計好層級結構。所以這里突然跳轉到分層。
上個圖:


我們自己定了幾個原則。
業務組件之間不能有依賴關系。
按照圖示不能跨層依賴。
所謂弱業務組件就是包含著少部分業務,并且可以在這個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上展示。
我們還可以創建一些弱業務組件給業務組件提供功能。當然了,不能夠濫用。需要準確劃分職責。
最后,代碼大概是這樣的:
@implementation PBBasicProviderModuleYTXMODULE_EXTERN(){}+ (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(nullable NSDictionary *)launchOptions{ [self setupThirdParty:application didFinishLaunchingWithOptions:launchOptions]; [self setupBasic:application didFinishLaunchingWithOptions:launchOptions]; return YES;}+ (void) setupThirdParty:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ [self setupEaseMob:application didFinishLaunchingWithOptions:launchOptions]; [self setupTalkingData]; [self setupAdTalkingData]; [self setupShareSDK]; [self setupJSPatch]; [self setupUmeng];// [self setupAdhoc]; });}+ (void) setupBasic:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{ [self registerBasic]; [self autoIncrementOpenAppCount]; [self setupScreenShowManager]; [self setupYTXAnalytics]; [self setupRemoteHook];}+ (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"
    主App中的業務組件版本號的Main.Minor要和主App版本保持一致。
    參考: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.podspecdownload_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。

    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 <Pod庫名稱> --template-url="http://gitlab.baidao.com/pods/ytx-pod-template"
第二步:
創建dev分支。用來開發。
第三步:
每次push dev的時候會觸發runner自動跑Stage: Init Lint(中的test)


第四步:
1.準備發布Pod庫。修改podspec的版本號,打上相應tag。 2.使用merge_request.sh向master提交一個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等是因為這個時候在做測試。

第七步:
主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就是并行的)


感謝徐川對本文的審校。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,268評論 2 375

推薦閱讀更多精彩內容