作者:點燃那火焰
續命
一個程序員最氣憤的事是什麼?沒有文檔!
一個程序員最討厭的事是什麼?寫文檔!
以上被稱為“程序員的糾結”,Unbelievable!最近我就趕上這麼糾結的事了,公司有一個項目,姑且稱之為“起死回生項目”吧,成立於兩年前,當時已經上架,但效果不好,又被迫下架關項目,但是人生就是這麼奇妙,兩年後時來運轉,市場迫切需要的東西正是這個“起死回生項目”,於是它+1s,咳咳,不,是起死回生了,然而當時所有的項目組成員均已離職,沒有文檔!沒有人交接!
誰來接手呢?領導說交給我,筆者當時正在另一個項目組上,當時我就說我一個其他項目組的,怎麼能去起死回生項目組呢,領導對我說:“組織上已經決定了,由你來當總書。。。咳咳,由你來重啟這個項目”。我心中無限感慨,當時就念了兩句詩:“苟利國家生死以。。。咳咳”
於是我就來到了這個坑爹的項目組,哦,對了該項目組只有我一個人!
而此時的我,剛剛學會iOS一年。。。。。
面臨的問題
作為一個有責任有擔當的新世界好青年,組織既然已經決定了,我又是黨員,這時候我不上誰上呢?!帶著這樣的決心,我開始了奇妙的重啟之旅。
首先,獲得帶權限,從服務器上下載來代碼,發現項目竟然運行不起來!?
一大堆Warning、Error!!
what!?
不是說已經上線了嗎?我看了下提交日志,最後一條是修改build號,發布到App Store了,怎麼會跑不起來呢?
仔細再看報錯,大多數是缺少文件類的,就是缺少framework、第三方庫之類的,但是奇怪的是之前確實可以跑得啊,於是通過N層關系找到原來的項目組成員,是的,在電話中得知,這個項目的依賴庫管理當時采用的是靜態庫方法,如今的小伙伴用cocoapods用的飛起,一句pod update搞定所有,然而當年,我不知道cocoapods發布了沒有,至少這個項目沒有采用,那麼如何管理眾多的第三方庫和組件呢?
答案是另起一個項目,把所需組件和庫,編譯成一個CommonLib Frameworks,再在主項目中導入framework。如今網上還能找到怎麼制作framework的文檔,不再贅述,大家找吧。
如下圖,這是我已經導入了一部分的截圖
缺少的Framework
原來是沒有導入Framework,才會出現的error,知道原因就好辦,我又找到這個Framework的代碼,加入進來,果然OK了,至此,我們終於build success。
同時也發現,該項目當時支持的最低版本是iOS 6.1,嗯,是的,在iOS 10.2發布的時候,這個項目還停留在6.1
最低支持版本
撸起袖子,加油干
好了,吐槽完了,該干的還得干呢,於是我開始思考,如何接收這樣的項目呢?
上干貨!
升級遷移計劃
以上是我的思維導圖,當然是邊做邊補充的,就像阿甘說的,你永遠不知道下一個坑在哪裡,走一步看一步了
下面就按照思維導圖的順序來做一一介紹!
1.遷移到Cocoapod
1.1 問題
依賴庫不易管理,自己人肉的方式管理依賴庫的痛苦大家都懂,這也是cocoapods盛行的原因
沒有版本管理,你只能去打開工程,肉眼分辨當時用的是哪個版本,例如MJRefresh已經更新到3.0,而項目中的是什麼版本呢,不知道
解決方法當然就是如標題所說,遷移到Cocoapod
1.2 遷移
思維導圖上,可以看到我面臨的問題
依賴庫統計
我必須知道項目中使用到了哪些第三方庫,哪些有Cocoapods版本,哪些沒有,這部分,我采用的本辦法,打開這個工程,人肉統計,有下圖這麼多!!!
依賴庫
呵呵!
沒辦法,只能去這麼做,這時候我們又發現,這麼多第三方庫,有些是沒有用到的,如何發現?就是去全局搜索咯
我猜是某些組件不再用了,但是沒有及時清理,於是留給後人很多無用代碼,也提醒讀者,注意這一點,不要害人害己啊
最終清理後的組件,不如上圖這麼多,大概二十個左右,整理成一個podfile文件,如下
注意,這裡每個組件名後,都有指定版本號,在下一節切換中有提到,如何確定版本號
podfile
這部分用到最多的就是cocoapods的搜索功能
pod search AFNetworking
切換
現在准備工作完畢,就是要切換了!
首先,需要去除工程配置文件裡面的很多配置項,原則就是靜態庫需要配置上的,這裡都得刪除,目標是把就項目中的所有相關CommonLibARC的配置清理干淨
例如下面的,配置很多很雜我這裡就不一一截圖,僅上圖2張供參考
圖1
圖2
然後,就是Cocoapods的部分了,網絡上有大量教程,我們需要建立podfile,再用pod install命令。這樣會生成一個xcworkspace文件,以後需要從這裡打開工程,如下圖,所有第三方組件被管理在Pods工程下
目錄
於此同時,這時也會產生大量的error,原因是podfile中需要制定庫的版本,而我們並不知道兩年前使用的什麼版本,隨著版本的變化,方法名可能會變動,甚至被干掉,這樣build必然失敗。
我的方法是:
1.用pod search,看看組件有哪些版本,先選一個比較穩定的大版本,比如,MJRefresh最新是3.1.12,從版本號和時間推算,當時工程中肯定是特別老的,可能是1.x,但是考慮到時間推移,組件也是有必要的升級,我最終選擇了3.0版本,這個版本使用者多,且並不算太老舊
pod search
果然產生大量方法名變動,沒轍,自己人肉一一修改,舉例如下
MJRefresh 方法名變動
2. error層出不窮
我進行到這一步是最痛苦的,因為連續幾天都是無法編譯成功的,因為當編譯器遇到一些error後就不在往下編譯了,因此你看到比如3個error,解決了他們,再build,發現又多出來5個,這種更多是精神上的折磨,耐性的消磨。而且我沒法提交代碼,因為每次我只解決了一部分問題,只能先暫存到本地,到編譯成功了才能正式提交到remote。
3. CocoaLumberjack--->DDLog
這裡特別要提一下CocoaLumberjack,這是一個集快捷、簡單、強大和靈活於一身的日志框架,我們主要用到的是DDlog,這是項目原本就在用的組件,但是DDlog需要定義一個日志級別,低於這個級別的log不再顯示。
在源代碼中,是每個用到的地方都在.m文件都定義一次,全局搜索會發現很多同樣的代碼, 顯得啰嗦
很多重復定義
定義日志級別
因此,我將其改到了PCH文件中,在appdelegate中定義其級別,這樣只需一次,全局通用
4. Crashlytics
這裡多提一次Crashlytics,這是個非常好用的統計Crash的工具,植入方便,使用簡潔,5分鐘搞定,強烈安利一發。原項目也有使用,不過賬號密碼沒有移交下來,我乘此機會將其更新到了最新版本
好了,至此,所有的遷移工作已經完成,我興奮的按下Command+B,Build Success!!!
然而茫茫多的warning還沒有解決呢
5. 組件過渡方法
有部分組件,牽扯范圍廣,替換起來特別麻煩,為了不影響進度此時我決定,先跑起來,後面再慢慢優化,於是,采用的了過渡方法,先不用cocoapod管理,但是Framework必須刪除,否則又會有沖突,怎麼辦呢
簡單粗暴的拖拽,直接把組件文件夾add進project!
先跑起來再說啊!
2 iOS 8.0 升級
最低版本
此時的項目,歷經歲月的滄桑,他終於跑起來了,但是項目依然停留在歷史的那個時刻,iOS 6.1!!!!
然後,我們再到蘋果官網,查看各版本占比,如圖,截止2017.01.04號,額,已經看不到8以下的,加一塊才6%
iOS版本占比
於是,我又開始了新的征程,升級到iOS 8.0,為啥是8.0呢?
步子大了怕扯到蛋!
如最上面的思維導圖所示,warning大部分集中在以下幾個地方
2.1 Method Deprecation
蘋果也是個坑爹的公司,隨著iOS版本的升級,需要系統原生的類和方法都發生了變化,有了新的方法替代,系統會提示我們改掉,又是個苦力活,沒辦法一一去改吧,此項目中碰到最多的是,UILabel的方法,用於計算字體大小
deprecated warning
deprecated warning
2.2 Format String Issue
這類比較好解決,大多數是系統可以幫忙解決,相信你們一定碰到過
Format String Issue
2.3 Conflicting Return Type
這個和第一種類似,都系統方法升級導致的,依照提示改過來即可
Conflicting Return Type
2.4 missing [super awakeFromNib]
這是坑爹的代碼風格導致,所有的UITableViewCell都沒有[super awakeFromNib]方法。
大家知道,我們在工程實踐中有RootClass,比如RootTableViewCell,所有cell都繼承於RootClass,在RootClass實現一些基本的、全局的屬性和方法,通常我們寫的就是awakeFromNib方法,缺失這個方法使得所有子類沒有繼承到父類的全局設置。
沒二話,必須加上
missing [super awakeFromNib]
missing [super awakeFromNib]
2.5 ARC bug
這個Bug估計是手抖寫錯了,但是排查起來挺麻煩的。
現象是某個頁面在debug的時候正常,但是打包安裝到手機上就會閃退,由於調試時一直是好的,所以抓不到Crash,這時候就是Crashlytics大顯身手的時候,我們登錄Crashlytics的網站,可以清楚的看到崩潰日志,如下圖,這裡顯示崩潰的原因是unrecognized selector,也就說一個對象調用了未知方法,按照Log指示,其實是一個NSDictionary調用了 objectAtIndex:方法
Crashlytics崩潰日志
再仔細看下去其實日志中還有具體crash的行數在305行,這行代碼如下:
代碼
確實是調用了objectAtIndex方法,可是這個yLabelsStringArray確實是一個NSArray啊!怎麼會變成NSDictionary了呢?錯在哪裡呢
數組屬性
仔細看的同學會發現,這個屬性的ARC參數是assign,大家知道assign是用於非指針變量,用於基礎數據類型,並不會對引用計數+1,也就說這個NSArray被創建後,就立馬被釋放了,這才導致崩潰,如此只有改成strong即可
改成strong
至此,整個項目build success!!
3 增加Log
好了,我們已經將一個老舊的iOS工程,成功升級成最常用的工程,但是迄今我們甚至沒有去了解整個項目是干嘛的,也就是說我們根本沒有了解到業務邏輯,由於文檔缺失,如何掌握業務邏輯成了大麻煩,我們只能通過自己把玩這個App,每個頁面去點點,去操作。於是為了方便快速掌握代碼架構和業務了解,需要Log來幫助我們理解項目
而原工程幾乎沒有任何Log。。。。
3.1 ViewWillAppeared/ViewdidLoad
所幸的是,這個工程的所有的ViewController都是繼承於一個RootController,於是在BaseViewController中,加入log,這我們在Debug時,隨著操作,我們就能知道哪個頁面對應哪個ViewController,這裡就用到了上面提到的DDLog
#ifdef DEBUG DDLogInfo(@"%@ viewDidLoad", [self class]); #endif #ifdef DEBUG DDLogInfo(@"%@ viewWillAppear", [self class]); #endif
3.2 Network Request Log
同樣的,我們對於接口調用也是一無所知,必須在網絡也打上Log,為了方便調試,我打印出了URL、Method(GET/POST)、參數、Header
多提一句為啥打印出Header,因為Header中有access token,這樣我們可以使用Postman來做需要登錄的接口調試,而不必在app點擊來觸發請求
Network Log
同樣的,安利一下Postman,非常好用的網絡調試工具
Postman
3.3 DB file path
工程中還有數據庫,在用模擬器調試的時候,其實我們是可以打開運行中App的數據庫的,只需打印出database的文件路徑即可,方便我們未來的調試
4 無效代碼移除
Log完成後,我們可以安心的來整理業務邏輯了。
跟需求方溝通後,app中有些功能完全可以移除掉了,接下來就是刪代碼的節奏了
這部分比較簡單,無用代碼找出來,Delete即可,還是苦力活
5 工程整理
5.1 模塊化目錄
工程中的目錄結構也是一個需要注意的地方,我采用的是使用最廣泛的MVC結構
目錄結構
5.2 網絡層整理
工程采用的是最常見的AFNetworking,這部分依然是體力活
首先需要整理出服務器地址,包括Dev(開發)、Stg(上線)、Pro(生產)三個環境
需要整理出所有的接口文檔,這就是靠Network Log了
網絡層邏輯整理,需要閱讀代碼,理解原本是怎麼構建出網絡層的,這部分可以暫放一會
5.3 數據庫整理
項目的許多數據是存在數據庫裡面的,從依賴庫來看,使用的是FMDB,也是比較常見的開源庫
我使用的是MesaSQLite作為client
這部分工作,需要有人配合,暫時放一放
6 storyboard臃腫
原項目中,所以的ViewController,注意,是所有的ViewController都在一個storyboard裡,所以當你想打開這個storyboard時,需要等,嗯,大約3min,給大家看一看,注意最下面我已經縮放到6.25%了,這樣一個龐大、臃腫的storyboard,即不好管理,也不會擴展,打開它還有嚴重的卡頓
storyboard
這部分我暫時寫一下思路,因為還沒有實施
6.1 方案一:storyboard reference
為了解決這個問題,在 iOS 9 中蘋果介紹了 Storyboard References 這個概念。Storyboard References 允許你從 segue 中引用其他 storyboard 中的 viewController。這意味中你可以保持不同功能模塊化,同時 Storyboard 的體積變小並易與管理。不僅容易理解了,和團隊一起工作時,合並(工作成果)也變的簡單了。
但是我們的項目目前還在8.0,需要考慮一下是否升級到9.0
6.2 方案二:storyboard拆分成xib
Storyboard的好處是,可以一眼看出app的邏輯架構,對於理解整個項目各個頁面之間的跳轉是有好處的,但是壞處顯而易見,他無法管理一個龐大的項目,因此很多工程采用的方案是xib,一個ViewController,對應一個xib,摒棄了storyboard,這樣擴展起來特別方便
但是問題是:原工程storyboard中,可能有三十多個ViewController,如何遷移過去是個麻煩的事