前言
本文較長(zhǎng)(5000字左右)子眶,建議閱讀時(shí)間: 20min+
一個(gè)iOS App的穩(wěn)定性,主要決定于整體的系統(tǒng)架構(gòu)設(shè)計(jì)歼冰,同時(shí)也不可忽略編程的細(xì)節(jié)失晴,正所謂“千里之堤剧腻,潰于蟻穴”,一旦考慮不周涂屁,看似無關(guān)緊要的代碼片段可能會(huì)帶來整體軟件系統(tǒng)的崩潰书在。尤其因?yàn)樘O果限制了熱更新機(jī)制,App本身的穩(wěn)定性及容錯(cuò)性就顯的更加重要拆又,之前可以通過發(fā)布熱補(bǔ)丁的方式解決線上代碼問題儒旬,現(xiàn)在就需要在提交之前對(duì)App開發(fā)周期內(nèi)的各個(gè)指標(biāo)進(jìn)行實(shí)時(shí)監(jiān)測(cè),盡量讓問題暴漏在開發(fā)階段帖族,然后及時(shí)修復(fù)栈源,減少線上出問題的幾率。針對(duì)一個(gè)App的開發(fā)周期竖般,它的穩(wěn)定性指標(biāo)主要有以下幾個(gè)環(huán)節(jié)構(gòu)成甚垦,用一個(gè)腦圖表示如下:
1 開發(fā)過程
開發(fā)過程中,主要是通過監(jiān)控內(nèi)存使用及泄露,CPU使用率艰亮,F(xiàn)PS闭翩,啟動(dòng)時(shí)間等指標(biāo),以及常見的UI的主線程監(jiān)測(cè)迄埃,NSAssert斷言等男杈,最好能在Debug模式下,實(shí)時(shí)顯示在界面上调俘,針對(duì)出現(xiàn)的問題及早解決。
內(nèi)存問題
內(nèi)存問題主要包括兩個(gè)部分旺垒,一個(gè)是iOS中常見循環(huán)引用導(dǎo)致的內(nèi)存泄露 彩库,另外就是大量數(shù)據(jù)加載及使用導(dǎo)致的內(nèi)存警告。
mmap
雖然蘋果并沒有明確每個(gè)App在運(yùn)行期間可以使用的內(nèi)存最大值先蒋,但是有開發(fā)者進(jìn)行了實(shí)驗(yàn)和統(tǒng)計(jì)骇钦,一般在占用系統(tǒng)內(nèi)存超過20%的時(shí)候會(huì)有內(nèi)存警告,而超過50%的時(shí)候竞漾,就很容易Crash了眯搭,所以內(nèi)存使用率還是盡量要少,對(duì)于數(shù)據(jù)量比較大的應(yīng)用业岁,可以采用分步加載數(shù)據(jù)的方式鳞仙,或者采用mmap方式。mmap 是使用邏輯內(nèi)存對(duì)磁盤文件進(jìn)行映射笔时,中間只是進(jìn)行映射沒有任何拷貝操作棍好,避免了寫文件的數(shù)據(jù)拷貝。 操作內(nèi)存就相當(dāng)于在操作文件允耿,避免了內(nèi)核空間和用戶空間的頻繁切換借笙。之前在開發(fā)輸入法的時(shí)候 ,詞庫的加載也是使用mmap方式较锡,可以有效降低App的內(nèi)存占用率业稼,具體使用可以參考鏈接第一篇文章。
循環(huán)引用
循環(huán)引用是iOS開發(fā)中經(jīng)常遇到的問題蚂蕴,尤其對(duì)于新手來說是個(gè)頭疼的問題低散。循環(huán)引用對(duì)App有潛在的危害,會(huì)使內(nèi)存消耗過高掂墓,性能變差和Crash等谦纱,iOS常見的內(nèi)存主要以下三種情況:
Delegate
代理協(xié)議是一個(gè)最典型的場(chǎng)景,需要你使用弱引用來避免循環(huán)引用君编。ARC時(shí)代跨嘉,需要將代理聲明為weak是一個(gè)即好又安全的做法:
@property (nonatomic, weak) id <MyCustomDelegate> delegate;
復(fù)制代碼
NSTimer
NSTimer我們開發(fā)中會(huì)用到很多,比如下面一段代碼
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self
selector:@selector(doSomeThing)
userInfo:nil
repeats:YES];
}
- (void)doSomeThing {
}
- (void)dealloc {
[self.timer invalidate];
self.timer = nil;
}
復(fù)制代碼
這是典型的循環(huán)引用吃嘿,因?yàn)閠imer會(huì)強(qiáng)引用self祠乃,而self又持有了timer梦重,所有就造成了循環(huán)引用。那有人可能會(huì)說亮瓷,我使用一個(gè)weak指針琴拧,比如
__weak typeof(self) weakSelf = self;
self.mytimer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(doSomeThing) userInfo:nil repeats:YES];
復(fù)制代碼
但是其實(shí)并沒有用,因?yàn)椴还苁莣eakSelf還是strongSelf嘱支,最終在NSTimer內(nèi)部都會(huì)重新生成一個(gè)新的指針指向self蚓胸,這是一個(gè)強(qiáng)引用的指針,結(jié)果就會(huì)導(dǎo)致循環(huán)引用除师。那怎么解決呢沛膳?主要有如下三種方式:
- 使用類方法
- 使用weakProxy
- 使用GCD timer
具體如何使用,我就不做具體的介紹汛聚,網(wǎng)上有很多可以參考锹安。
Block
Block的循環(huán)引用,主要是發(fā)生在ViewController中持有了block倚舀,比如:
@property (nonatomic, copy) LFCallbackBlock callbackBlock;
復(fù)制代碼
同時(shí)在對(duì)callbackBlock進(jìn)行賦值的時(shí)候又調(diào)用了ViewController的方法叹哭,比如:
self.callbackBlock = ^{
[self doSomething];
}];
復(fù)制代碼
就會(huì)發(fā)生循環(huán)引用,因?yàn)椋篤iewController->強(qiáng)引用了callback->強(qiáng)引用了ViewController痕貌,解決方法也很簡(jiǎn)單:
__weak __typeof(self) weakSelf = self;
self.callbackBlock = ^{
[weakSelf doSomething];
}];
復(fù)制代碼
原因是使用MRC管理內(nèi)存時(shí)风罩,Block的內(nèi)存管理需要區(qū)分是Global(全局)、Stack(棧)還是Heap(堆)芯侥,而在使用了ARC之后泊交,蘋果自動(dòng)會(huì)將所有原本應(yīng)該放在棧中的Block全部放到堆中。全局的Block比較簡(jiǎn)單柱查,凡是沒有引用到Block作用域外面的參數(shù)的Block都會(huì)放到全局內(nèi)存塊中廓俭,在全局內(nèi)存塊的Block不用考慮內(nèi)存管理問題。(放在全局內(nèi)存塊是為了在之后再次調(diào)用該Block時(shí)能快速反應(yīng)唉工,當(dāng)然沒有調(diào)用外部參數(shù)的Block根本不會(huì)出現(xiàn)內(nèi)存管理問題)研乒。
所以Block的內(nèi)存管理出現(xiàn)問題的,絕大部分都是在堆內(nèi)存中的Block出現(xiàn)了問題淋硝。默認(rèn)情況下雹熬,Block初始化都是在棧上的,但可能隨時(shí)被收回谣膳,通過將Block類型聲明為copy類型竿报,這樣對(duì)Block賦值的時(shí)候,會(huì)進(jìn)行copy操作继谚,copy到堆上烈菌,如果里面有對(duì)self的引用,則會(huì)有一個(gè)強(qiáng)引用的指針指向self,就會(huì)發(fā)生循環(huán)引用芽世,如果采用weakSelf挚赊,內(nèi)部不會(huì)有強(qiáng)類型的指針,所以可以解決循環(huán)引用問題济瓢。
那是不是所有的block都會(huì)發(fā)生循環(huán)引用呢荠割?其實(shí)不然,比如UIView的類方法Block動(dòng)畫旺矾,NSArray等的類的遍歷方法蔑鹦,也都不會(huì)發(fā)生循環(huán)引用,因?yàn)楫?dāng)前控制器一般不會(huì)強(qiáng)引用一個(gè)類箕宙。
其他內(nèi)存問題
1 NSNotification addObserver之后举反,記得在dealloc里面添加remove;
2 動(dòng)畫的repeat count無限大,而且也不主動(dòng)停止動(dòng)畫扒吁,基本就等于無限循環(huán)了;
3 forwardingTargetForSelector返回了self。
內(nèi)存解決思路:
1 通過Instruments來查看leaks
2 集成Facebook開源的FBRetainCycleDetector
3 集成MLeaksFinder
具體原理及使用室囊,可以參考鏈接雕崩。
CPU使用率
CPU的使用也可以通過兩種方式來查看,一種是在調(diào)試的時(shí)候Xcode會(huì)有展示融撞,具體詳細(xì)信息可以進(jìn)入Instruments內(nèi)查看盼铁,通過查看Instruments的time profile來定位并解決問題。另一種常見的方法是通過代碼讀取CPU使用率尝偎,然后顯示在App的調(diào)試面板上饶火,可以在Debug環(huán)境下顯示信息,具體代碼如下:
int result;
mib[0] = CTL_HW;
mib[1] = HW_CPU_FREQ;
length = sizeof(result);
if (sysctl(mib, 2, &result, &length, NULL, 0) < 0)
{
perror("getting cpu frequency");
}
printf("CPU Frequency = %u hz\n", result);
復(fù)制代碼
FPS監(jiān)控
目前主要使用CADisplayLink來監(jiān)控FPS致扯,CADisplayLink是一個(gè)能讓我們以和屏幕刷新率相同的頻率將內(nèi)容畫到屏幕上的定時(shí)器肤寝。我們?cè)趹?yīng)用中創(chuàng)建一個(gè)新的 CADisplayLink 對(duì)象,把它添加到一個(gè)runloop中抖僵,并給它提供一個(gè) target 和selector 在屏幕刷新的時(shí)候調(diào)用鲤看,需要注意的是添加到runloop的common mode里面,代碼如下:
- (void)setupDisplayLink {
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTicks:)];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)linkTicks:(CADisplayLink *)link
{
//執(zhí)行次數(shù)
_scheduleTimes ++;
//當(dāng)前時(shí)間戳
if(_timestamp == 0){
_timestamp = link.timestamp;
}
CFTimeInterval timePassed = link.timestamp - _timestamp;
if(timePassed >= 1.f)
//fps
CGFloat fps = _scheduleTimes/timePassed;
printf("fps:%.1f, timePassed:%f\n", fps, timePassed);
}
}
復(fù)制代碼
啟動(dòng)時(shí)間
點(diǎn)評(píng)App里面本身就包含了很多復(fù)雜的業(yè)務(wù)耍群,比如外賣义桂、團(tuán)購(gòu)、到綜和酒店等蹈垢,同時(shí)還引入了很多第三方SDK比如微信慷吊、QQ、微博等曹抬,在App初始化的時(shí)候溉瓶,很多SDK及業(yè)務(wù)也開始初始化,這就會(huì)拖慢應(yīng)用的啟動(dòng)時(shí)間。 如下主要參考了今日頭條iOS客戶端啟動(dòng)速度優(yōu)化
App的啟動(dòng)時(shí)間t(App總啟動(dòng)時(shí)間) = t1(main()之前的加載時(shí)間) + t2(main()之后的加載時(shí)間)嚷闭。
t1 = 系統(tǒng)dylib(動(dòng)態(tài)鏈接庫)和自身App可執(zhí)行文件的加載攒岛;
t2 = main方法執(zhí)行之后到AppDelegate類中的- (BOOL)Application:(UIApplication *)Application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法執(zhí)行結(jié)束前這段時(shí)間,主要是構(gòu)建第一個(gè)界面胞锰,并完成渲染展示灾锯。
復(fù)制代碼
針對(duì)t1的優(yōu)化,優(yōu)化主要有如下:
- 減少不必要的framework嗅榕,因?yàn)閯?dòng)態(tài)鏈接比較耗時(shí)顺饮;
- 檢查framework應(yīng)當(dāng)設(shè)為optional和required,如果該framework在當(dāng)前App支持的所有iOS系統(tǒng)版本都存在凌那,那么就設(shè)為required兼雄,否則就設(shè)為optional,因?yàn)閛ptional會(huì)有些額外的檢查帽蝶;
- 合并或者刪減一些OC類赦肋,這些我會(huì)在后續(xù)的靜態(tài)檢查中進(jìn)行詳解;
針對(duì)t2的時(shí)間優(yōu)化励稳,可以采用:
- 異步初始化部分操作佃乘,比如網(wǎng)絡(luò),數(shù)據(jù)讀染阅帷趣避;
- 采用延遲加載或者懶加載某些視圖,圖片等的初始化操作新翎;
- 對(duì)與圖片展示類的App程帕,可以將解碼的圖片保存到本地,下次啟動(dòng)時(shí)直接加載解碼后的圖片地啰;
- 對(duì)實(shí)現(xiàn)了+load()方法的類進(jìn)行分析愁拭,盡量將load里的代碼延后調(diào)用。
UI的主線程監(jiān)測(cè)
我們都知道iOS的UI的操作一定是在主線程進(jìn)行亏吝,該監(jiān)測(cè)可以通過hook UIView的如下三個(gè)方法
-setNeedsLayout敛苇,
-setNeedsDisplay,
-setNeedsDisplayInRect
復(fù)制代碼
確保它們都是在主線程執(zhí)行顺呕。子線程操作UI可能會(huì)引起什么問題枫攀,蘋果說得并不清楚,但是在實(shí)際開發(fā)中株茶,我們經(jīng)常會(huì)遇到整個(gè)App的動(dòng)畫丟失来涨,很大原因就是UI操作不是在主線程導(dǎo)致。
2 靜態(tài)分析過程
靜態(tài)分析在這里启盛,我主要介紹兩方面蹦掐,一個(gè)是正常的code review機(jī)制技羔,另外一個(gè)就是代碼靜態(tài)檢查工具
code review
組內(nèi)的code review機(jī)制,可以參考團(tuán)隊(duì)之前的OpenDoc - 前端團(tuán)隊(duì)CodeReview制度卧抗,iOS客戶端開發(fā)藤滥,會(huì)在此基礎(chǔ)上進(jìn)行一些常見手誤及Crash情況的重點(diǎn)標(biāo)記,比如:
1 我們開發(fā)中首先都是在測(cè)試環(huán)境開發(fā)社裆,開發(fā)時(shí)可以將測(cè)試環(huán)境的url寫死到代碼中拙绊,但是在提交代碼的時(shí)候一定要將他改為線上環(huán)境的url,這個(gè)就可以通過gitlab中的重點(diǎn)比較部分字符串泳秀,給提交者一個(gè)強(qiáng)力的提示标沪;
2 其他常見Crash的重點(diǎn)檢查,比如NSMutableString/NSMutableArray/NSMutableDictionary/NSMutableSet 等類下標(biāo)越界判斷保護(hù)嗜傅,或者 append/insert/add nil對(duì)象的保護(hù)金句;
3 ARC下的release操作,UITableViewCell返回nil吕嘀,以及前面介紹的常見的循環(huán)引用等违寞。
code review機(jī)制,一方面是依賴寫代碼者的代碼習(xí)慣及質(zhì)量偶房,另一名依賴審查者的經(jīng)驗(yàn)和細(xì)心程度坞靶,即使讓多人revew,也可能會(huì)漏過一些錯(cuò)誤蝴悉,所以我們又添加了代碼的靜態(tài)檢查。
代碼靜態(tài)檢查
代碼靜態(tài)分析(Static Program Analysis)是指在不運(yùn)行程序的條件下瘾敢,由代碼靜態(tài)分析工具自動(dòng)對(duì)程序進(jìn)行分析的方法. iOS常見的靜態(tài)掃描工具有Clang Static Analyzer拍冠、OCLint、Infer簇抵,這些主要是用來檢查可能存在的問題庆杜,還有Deploymate用來檢查api的兼容性。
Clang Static Analyzer
Clang Static Analyzer是一款靜態(tài)代碼掃描工具碟摆,專門用于針對(duì)C晃财,C++和Objective-C的程序進(jìn)行分析。已經(jīng)被Xcode集成典蜕,可以直接使用Xcode進(jìn)行靜態(tài)代碼掃描分析断盛,Clang默認(rèn)的配置主要是空指針檢測(cè),類型轉(zhuǎn)換檢測(cè)愉舔,空判斷檢測(cè)钢猛,內(nèi)存泄漏檢測(cè)這種等問題。如果需要更多的配置轩缤,可以使用開源的Clang項(xiàng)目命迈,然后集成到自己的CI上贩绕。
OCLint
OCLint是一個(gè)強(qiáng)大的靜態(tài)代碼分析工具,可以用來提高代碼質(zhì)量壶愤,查找潛在的bug淑倾,主要針對(duì) C、C++和Objective-C的靜態(tài)分析征椒。功能非常強(qiáng)大娇哆,而且是出自國(guó)人之手。OCLint基于 Clang 輸出的抽象語法樹對(duì)代碼進(jìn)行靜態(tài)分析陕靠,支持與現(xiàn)有的CI集成迂尝,部署之后基本不需要維護(hù),簡(jiǎn)單方便剪芥。
OCLint可以發(fā)現(xiàn)這些問題
- 可能的bug - 空的 if / else / try / catch / finally 語句
- 未使用的代碼 - 未使用的局部變量和參數(shù)
- 復(fù)雜的代碼 - 高圈復(fù)雜度, NPath復(fù)雜, 高NCSS
- 冗余代碼 - 多余的if語句和無用的括號(hào)
- 壞味道的代碼 - 過長(zhǎng)的方法和過長(zhǎng)的參數(shù)列表
- 不好的使用 - 倒邏輯和入?yún)⒅匦沦x值
對(duì)于OCLint的與原理和部署方法垄开,這里不做細(xì)講解,主要是每次提交代碼后税肪,可以在打包的過程中進(jìn)行代碼檢查溉躲,及早發(fā)現(xiàn)有問題的代碼。當(dāng)然也可以在合并代碼之前執(zhí)行對(duì)應(yīng)的檢查益兄,如果檢查不通過锻梳,不能合并代碼,這樣檢查的力度更大净捅。
Infer
Infer facebook開源的靜態(tài)分析工具疑枯,Infer可以分析 Objective-C, Java 或者 C 代碼蛔六,報(bào)告潛在的問題荆永。Infer效率高,規(guī)模大国章,幾分鐘能掃描數(shù)千行代碼具钥; C/OC中捕捉的bug類型主要有:
1:Resource leak
2:Memory leak
3:Null dereference
4:Premature nil termination argument
復(fù)制代碼
只在 OC中捕捉的bug類型
1:Retain cycle
2:Parameter not null checked
3:Ivar not null checked
復(fù)制代碼
結(jié)論
Clang Static Analyzer和Xcode集成度更高、更好用液兽,支持命令行形式骂删,并且能夠用于持續(xù)集成。OCLint有更多的檢查規(guī)則和定制四啰,和很多工具集成宁玫,也同樣可用于持續(xù)集成。Infer效率高柑晒,規(guī)模大撬统,幾分鐘能掃描數(shù)千行代碼;支持增量及非增量分析敦迄;分解分析恋追,整合輸出結(jié)果凭迹。infer能將代碼分解,小范圍分析后再將結(jié)果整合在一起苦囱,兼顧分析的深度和速度嗅绸,所以根據(jù)自己的項(xiàng)目特點(diǎn),選擇合適的檢查工具對(duì)代碼進(jìn)行檢查撕彤,減少人力review成本鱼鸠,保證代碼質(zhì)量,最大限度的避免運(yùn)行錯(cuò)誤羹铅。
3 測(cè)試過程
前面介紹了很多指標(biāo)的監(jiān)測(cè)蚀狰,代碼靜態(tài)檢查,這些都是性能相關(guān)的职员,真正決定一個(gè)App功能穩(wěn)定是否的是測(cè)試環(huán)節(jié)麻蹋。測(cè)試是發(fā)布之前的最后一道卡,如果bug不能在測(cè)試中發(fā)現(xiàn)焊切,那么最終就會(huì)觸達(dá)用戶扮授,所以一個(gè)App的穩(wěn)定性,很大程度決定它的測(cè)試過程专肪。iOS App的測(cè)試包括以下幾個(gè)層次:?jiǎn)卧獪y(cè)試刹勃,UI測(cè)試,功能測(cè)試嚎尤,異常測(cè)試荔仁。
單元測(cè)試
XCTest是蘋果官方提供的單元測(cè)試框架,與Xcode集成在一起芽死,由此蘋果提供了很詳細(xì)的文檔XCTest乏梁。
Xcode單元測(cè)試包含在一個(gè)XCTestCase的子類中。依據(jù)約束收奔,每一個(gè) XCTestCase 子類封裝一個(gè)特殊的有關(guān)聯(lián)的集合,例如一個(gè)功能滓玖、用例或者一個(gè)程序流坪哄。同時(shí)還提供了XCTestExpectation來處理異步任務(wù)的測(cè)試,以及性能測(cè)試measureBlock()势篡,還包括很多第三方測(cè)試框架比如:KiWi翩肌,Quick,Specta等禁悠,以及常用的mock框架OCMock念祭。
單元測(cè)試的目的是將程序中所有的源代碼,隔離成最小的可測(cè)試單元碍侦,以確保每個(gè)單元的正確性粱坤,如果每個(gè)單元都能保證正確隶糕,就能保證應(yīng)用程序整體相當(dāng)程度的正確性。但是在實(shí)際的操作過程中站玄,很多公司都很難徹底執(zhí)行單元測(cè)試枚驻,主要就是單元測(cè)試代碼量甚至大于功能開發(fā),比較難于維護(hù)株旷。
對(duì)于測(cè)試用例覆蓋度多少合適這個(gè)話題再登,也是仁者見仁智者見智,其實(shí)一個(gè)軟件覆蓋度在50%以上就可以稱為一個(gè)健壯的軟件了晾剖,要達(dá)到70锉矢,80這些已經(jīng)是非常難了,不過我們常見的一些第三方開源框架的測(cè)試用例覆蓋率還是非常高的齿尽,讓人咋舌沽损。例如,AFNNetWorking的覆蓋率高達(dá)87%,SDWebImage的覆蓋率高達(dá)77%雕什。
UI測(cè)試
Xcode7中新增了UI Test測(cè)試缠俺,UI測(cè)試是模擬用戶操作,進(jìn)而從業(yè)務(wù)處層面測(cè)試贷岸,常用第三方庫有KIF壹士,appium。關(guān)于XCTest的UI測(cè)試偿警,建議看看WWDC 2015的視頻UI Testing in Xcode躏救。 UI測(cè)試還有一個(gè)核心功能是UI Recording。選中一個(gè)UI測(cè)試用例螟蒸,然后點(diǎn)擊圖中的小紅點(diǎn)既可以開始UI Recoding盒使。你會(huì)發(fā)現(xiàn):隨著點(diǎn)擊模擬器,自動(dòng)合成了測(cè)試代碼七嫌。(通常自動(dòng)合成代碼后少办,還需要手動(dòng)的去調(diào)整)
[圖片上傳中...(image-bce7b8-1554696366212-78)]
<figcaption></figcaption>
功能測(cè)試
功能測(cè)試跟上述的UT和UI測(cè)試有一些相通的地方,首先針對(duì)各個(gè)模塊設(shè)計(jì)的功能诵原,測(cè)試是否達(dá)到產(chǎn)品的目的英妓,通常功能測(cè)試主要是測(cè)試及產(chǎn)品人員,然后還需要進(jìn)行專項(xiàng)測(cè)試绍赛,比如我們公司的云測(cè)平臺(tái)蔓纠,會(huì)對(duì)整個(gè)App的性能,穩(wěn)定性吗蚌,UI等都進(jìn)行整體評(píng)測(cè)腿倚,看是否達(dá)到標(biāo)準(zhǔn),對(duì)于大規(guī)模的活動(dòng)蚯妇,還需要進(jìn)行服務(wù)端的壓力測(cè)試敷燎,確保整個(gè)功能無異常暂筝。測(cè)試通過后,可以進(jìn)行estFlight測(cè)試懈叹,到最后正式發(fā)布乖杠。
功能測(cè)試還包括如下場(chǎng)景:系統(tǒng)兼容性測(cè)試,屏幕分辨率兼容性測(cè)試澄成,覆蓋安裝測(cè)試胧洒,UI是否符合設(shè)計(jì),消息推送等墨状,以及前面開發(fā)過程中需要監(jiān)控的內(nèi)存卫漫、cpu、電量肾砂、網(wǎng)絡(luò)流量列赎、冷啟動(dòng)時(shí)間、熱啟動(dòng)時(shí)間镐确、存儲(chǔ)包吝、安裝包的大小等測(cè)試。
異常測(cè)試
異常測(cè)試主要是針對(duì)一些不常規(guī)的操作
- 使用過程中的來電時(shí)及結(jié)束后源葫,界面顯示是否正常诗越;
- 狀態(tài)欄為兩倍高度時(shí),界面是否顯示正常息堂;
- 意外斷電后嚷狞,數(shù)據(jù)是否保存,數(shù)據(jù)是否有損害等荣堰;
- 設(shè)備充電時(shí)床未,不同電量時(shí)的App響應(yīng)速度及操作流暢度等;
- 其他App的相互切換振坚,前后臺(tái)轉(zhuǎn)換時(shí)薇搁,是否正常;
- 網(wǎng)絡(luò)變化時(shí)的提示渡八,弱網(wǎng)環(huán)境下的網(wǎng)絡(luò)請(qǐng)求成功率等啃洋;
- 各種monkey的隨機(jī)點(diǎn)擊,多點(diǎn)觸摸測(cè)試等是否正常呀狼;
- 更改系統(tǒng)時(shí)間裂允,字體大小损离,語言等顯示是否正常哥艇;
- 設(shè)備存儲(chǔ)不夠時(shí),是否能正常操作僻澎;
- ...
異常測(cè)試有很多貌踏,App針對(duì)自身的特點(diǎn)十饥,可以選擇性的進(jìn)行邊界和異常測(cè)試,也是保證App穩(wěn)定行的一個(gè)重要方面祖乳。
4 發(fā)布及監(jiān)控
因?yàn)橐苿?dòng)App的特點(diǎn)逗堵,即使我們通過了各種測(cè)試,產(chǎn)品最終發(fā)布后眷昆,還是會(huì)遇到很多問題蜒秤,比如Crash环肘,網(wǎng)絡(luò)失敗讶泰,數(shù)據(jù)損壞,賬號(hào)異常等等庭猩。針對(duì)已經(jīng)發(fā)布的App帅刊,主要有一下方式保證穩(wěn)定性:
熱修復(fù)
目前比較流行的熱修復(fù)方案都是基于JSPatch纸泡、React Native、Weex赖瞒、lua+wax女揭。
JSPatch能做到通過js調(diào)用和改寫OC方法。最根本的原因是 Objective-C 是動(dòng)態(tài)語言栏饮,OC上所有方法的調(diào)用/類的生成都通過 objective-c Runtime 在運(yùn)行時(shí)進(jìn)行吧兔,我們可以通過類名和方法名反射得到相應(yīng)的類和方法,也可以替換某個(gè)類的方法為新的實(shí)現(xiàn)抡爹,還可以新注冊(cè)一個(gè)類掩驱,為類添加方法。JSPatch 的原理就是:JS傳遞字符串給OC冬竟,OC通過 Runtime 接口調(diào)用和替換OC方法欧穴。
React Native 是從 Web 前端開發(fā)框架 React 延伸出來的解決方案,主要解決的問題是 Web 頁面在移動(dòng)端性能低的問題泵殴,React Native 讓開發(fā)者可以像開發(fā) Web 頁面那樣用 React 的方式開發(fā)功能涮帘,同時(shí)框架會(huì)通過 JavaScript 與 Objective-C 的通信讓界面使用原生組件渲染,讓開發(fā)出來的功能擁有原生App的性能和體驗(yàn)笑诅。
Weex阿里開源的调缨,基于Vue+Native的開發(fā)模式,跟RN的主要區(qū)別就在React和Vue的區(qū)別吆你,同時(shí)在RN的基礎(chǔ)上進(jìn)行了部分性能優(yōu)化弦叶,總體開發(fā)思路跟RN是比較像的。
但是在今年上半年妇多,蘋果以安全為理由伤哺,開始拒絕有熱修復(fù)功能的應(yīng)用,但其實(shí)蘋果拒的不是熱更新,拒的是從網(wǎng)絡(luò)下載代碼并修改應(yīng)用行為立莉,蘋果禁止的是“基于反射的熱更新“绢彤,而不是 “基于沙盒接口的熱更新”。而大部分框架(如 React Native蜓耻、weex)和游戲引擎(比如 Unity茫舶、Cocos2d-x等)都屬于后者,所以不在被警告范圍內(nèi)刹淌。而JSPatch因?yàn)樵趪?guó)內(nèi)大部分應(yīng)用來做熱更新修復(fù)bug的行為饶氏,所以才回被蘋果禁止。
降級(jí)
用戶使用App一段時(shí)間后有勾,可能會(huì)遇到這樣的情況:每次打開App時(shí)閃退嚷往,或者正常操作到某個(gè)界面時(shí)閃退,無法正常使用App柠衅。這樣的用戶體驗(yàn)十分糟糕皮仁,如果沒有一個(gè)好的解決方案,很容易被用戶刪除App菲宴,導(dǎo)致用戶量的流失贷祈。因?yàn)闊岣禄静荒苁褂茫蔷椭荒苁茿pp自身修復(fù)能力喝峦。目前常用的修復(fù)能力有:
- 啟動(dòng)Crash的監(jiān)控及修復(fù)
1 在應(yīng)用起來的時(shí)候势誊,記錄flag并保存本地,啟動(dòng)一個(gè)定時(shí)器谣蠢,比如5秒鐘內(nèi)粟耻,如果沒有發(fā)生Crash,則認(rèn)為用戶操作正常眉踱,清空本地flag挤忙。
2 下次啟動(dòng),發(fā)現(xiàn)有flag谈喳,則表明上次啟動(dòng)Crash册烈,如果flag數(shù)組越大,則說明Crash的次數(shù)越多婿禽,這樣就需要對(duì)整個(gè)App進(jìn)行降級(jí)處理赏僧,比如登出賬號(hào),清空Documents/Library/Caches目錄下的文件扭倾。
- 具體業(yè)務(wù)下的Crash及修復(fù)
針對(duì)某些具體業(yè)務(wù)Crash場(chǎng)景淀零,如果是上線的前端頁面引起的,可以先對(duì)前端功能進(jìn)行回滾膛壹,或者隱藏入口驾中,等修復(fù)完畢后再上線琼牧,如果是客戶端的某些異常,比如數(shù)據(jù)庫升遷問題哀卫,主要是進(jìn)行業(yè)務(wù)數(shù)據(jù)庫修復(fù),緩存文件的刪除撬槽,賬號(hào)退出等操作此改,盡量只修復(fù)此業(yè)務(wù)的相關(guān)的數(shù)據(jù)。
- 網(wǎng)絡(luò)降級(jí)
比如點(diǎn)評(píng)App侄柔,本身有CIP(公司內(nèi)部自己研發(fā)的)長(zhǎng)連接共啃,接入騰訊云的WNS長(zhǎng)連接,UDP連接暂题,HTTP短連接移剪,如果CIP服務(wù)器發(fā)生問題,可以及時(shí)切換到WNS連接薪者,或者降級(jí)到Http連接纵苛,保證網(wǎng)絡(luò)連接的成功率。
線上監(jiān)控
Crash監(jiān)控
Crash是對(duì)用戶來說是最糟糕的體驗(yàn)言津,Crash日志能夠記錄用戶閃退的崩潰日志及堆棧攻人,進(jìn)程線程信息,版本號(hào)悬槽,系統(tǒng)版本號(hào)怀吻,系統(tǒng)機(jī)型等有用信息,收集的信息越詳細(xì)初婆,越能夠幫助解決崩潰蓬坡,所以各大App都有自己崩潰日志收集系統(tǒng),或者也可以使用開源或者付費(fèi)的第三方Crash收集平臺(tái)磅叛。
端到端成功率監(jiān)控
端到端監(jiān)控是從客戶端App發(fā)出請(qǐng)求時(shí)計(jì)時(shí)屑咳,到App收到數(shù)據(jù)數(shù)據(jù)的成功率,統(tǒng)計(jì)對(duì)象是:網(wǎng)絡(luò)接口請(qǐng)求(包括H5頁面加載)的成敗和端到端延時(shí)情況弊琴。端到端監(jiān)控SDK提供了監(jiān)控上傳接口乔宿,調(diào)用SDK提供的監(jiān)控API可以將數(shù)據(jù)上報(bào)到監(jiān)控服務(wù)器中。
整個(gè)端到端監(jiān)控的可以在多個(gè)維度上做查詢端到端成功率访雪、響應(yīng)時(shí)間详瑞、訪問量的查詢,維度包括:返回碼臣缀、網(wǎng)絡(luò)坝橡、版本、平臺(tái)精置、地區(qū)计寇、運(yùn)營(yíng)商等。
用戶行為日志
用戶行為日志,主要記錄用戶在使用App過程中番宁,點(diǎn)擊元素的時(shí)間點(diǎn)元莫,瀏覽時(shí)長(zhǎng),跳轉(zhuǎn)流程等蝶押,然后基于此進(jìn)行用戶行為分析踱蠢,大部分應(yīng)用的推薦算法都是基于用戶行為日志來統(tǒng)計(jì)的。某些情況下棋电,Crash分析需要查詢用戶的行為日志茎截,獲取用戶使用App的流程,幫助解決Crash等其他問題赶盔。
代碼級(jí)日志
代碼級(jí)別的日志企锌,主要用來記錄一個(gè)App的性能相關(guān)的數(shù)據(jù),比如頁面打開速度于未,內(nèi)存使用率撕攒,CPU占用率,頁面的幀率烘浦,網(wǎng)絡(luò)流量打却,請(qǐng)求錯(cuò)誤統(tǒng)計(jì)等,通過收集相關(guān)的上下文信息谎倔,優(yōu)化App性能柳击。
總結(jié)
雖然現(xiàn)在市面上第三方平臺(tái)已經(jīng)很成熟,但是各大互聯(lián)公司都會(huì)自己開發(fā)線上監(jiān)控系統(tǒng)片习,這樣保證數(shù)據(jù)安全捌肴,同時(shí)更加靈活。因?yàn)橐苿?dòng)用戶的特點(diǎn)藕咏,在開發(fā)測(cè)試過程中状知,很難完全覆蓋所有用戶的全部場(chǎng)景,有些問題也只會(huì)在特定環(huán)境下才發(fā)生孽查,所以通過線上監(jiān)控平臺(tái)饥悴,通過日志回?fù)频葯C(jī)制,及時(shí)獲取特定場(chǎng)景的上下文環(huán)境盲再,結(jié)合數(shù)據(jù)分析西设,能夠及時(shí)發(fā)現(xiàn)問題,并后續(xù)修復(fù)答朋,提高App的穩(wěn)定性贷揽。
全文總結(jié)
本文主要從開發(fā)測(cè)試發(fā)布等流程來介紹了一個(gè)App穩(wěn)定性指標(biāo)及監(jiān)測(cè)方法,開發(fā)階段主要針對(duì)一些比較具體的指標(biāo)梦碗,靜態(tài)檢查主要是掃描代碼潛在問題禽绪,然后通過測(cè)試保證App功能的穩(wěn)定性蓖救,線上降級(jí)主要是在盡量不發(fā)版的情況下,進(jìn)行自修復(fù)印屁,配合線上監(jiān)控循捺,信息收集,用戶行為記錄雄人,方便后續(xù)問題修復(fù)及優(yōu)化从橘。本文觀點(diǎn)是作者從事iOS開發(fā)的一些經(jīng)驗(yàn),希望能對(duì)你有所幫助柠衍,觀點(diǎn)不同歡迎討論。
參考:
微信mars 的高性能日志模塊 xlog 基于 CADisplayLink 的 FPS 指示器詳解 今日頭條iOS客戶端啟動(dòng)速度優(yōu)化 微信讀書 iOS 性能優(yōu)化總結(jié) 移動(dòng)端監(jiān)控體系之技術(shù)原理剖析 美團(tuán)點(diǎn)評(píng)移動(dòng)網(wǎng)絡(luò)優(yōu)化實(shí)踐 iOS 啟動(dòng)連續(xù)閃退保護(hù)方案 微信 SQLite 數(shù)據(jù)庫修復(fù)實(shí)踐
作者:美團(tuán)點(diǎn)評(píng)點(diǎn)餐
鏈接:https://juejin.im/post/58ca0832a22b9d006418fe43
來源:掘金
著作權(quán)歸作者所有晶乔。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)珍坊,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。