1.啟動時間
應(yīng)用啟動時間長短對用戶第一次體驗至關(guān)重要晓猛,同時系統(tǒng)對應(yīng)用的啟動、恢復(fù)等狀態(tài)的運(yùn)行時間也有嚴(yán)格的要求,在應(yīng)用超時的情況下系統(tǒng)會直接關(guān)閉應(yīng)用轰枝。以下是幾個常見場景下系統(tǒng)對app運(yùn)行時間的要求:
- Launch 20秒
- Resume 10秒
- Suspend 10秒
- Quit 6秒
- Background Task 10分鐘
要獲取準(zhǔn)確的app啟動所需時間,最簡單的方法時首先在main.c中添加如下代碼:
CFAbsoluteTime StartTime;
int main(int argc, char **argv) {
StartTime = CFAbsoluteTimeGetCurrent();
然后在AppDelegate的回調(diào)方法application:didFinishLaunchingWithOptions中添加:
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@”Lauched in %f seconds.”, (CFAbsoluteTimeGetCurrent() – StartTime));
});
可能你會覺得為什么這樣可拿到系統(tǒng)啟動的時間组去,因為這個dispatch_async中提交的工作會在app主線程啟動后的下一個run lopp中運(yùn)行鞍陨,此時app已經(jīng)完成了載入并且將要顯示第一幀畫面,也就是系統(tǒng)會運(yùn)行到-[UIApplication _reportAppLaunchFinished]
之前从隆。
2.懶加載
當(dāng)需要顯示View的時候才去創(chuàng)建诚撵,而不是創(chuàng)建好所有View,在需要顯示的時候改變他的hidden屬性或則透明度键闺。
每個方案都有其優(yōu)缺點(diǎn):
第一種方案則相反-消耗更少內(nèi)存寿烟,但是會在點(diǎn)擊按鈕的時候比第一種稍顯卡頓。
第二種方案一開始就創(chuàng)建一個view會消耗內(nèi)存辛燥,然而這也會使你的app操作更流暢筛武。
具體選擇哪一種就看開發(fā)者的選擇了,你是愿意那空間換時間還是愿意拿時間換空間购桑,雖然現(xiàn)在時間越來越寶貴畅铭,空間相對于時間來說顯得沒那么重要,但是目前市場上還是有一部分用戶使用低端機(jī)的勃蜘,為了適配這些機(jī)型硕噩,兩者還是要綜合考慮的。
3.正確加載圖片
加載圖片常用的兩種方式:
[UIImage imageNamed:@"myImage"];
[UIImage imageWithContentsOfFile:@"myImage"];
第一種方法首先會到緩存中查找如果存在返回圖片對象缭贡,緩存中沒有就會從資源文件中加載并緩存到內(nèi)存中去炉擅。
第二種是從磁盤中讀取加載圖片。如果你要加載一個大圖片而且是一次性使用阳惹,那么就沒必要緩存這個圖片谍失,用imageWithContentsOfFile足矣,這樣不會浪費(fèi)內(nèi)存來緩存它莹汤。
4.盡量設(shè)置View為不透明
如果你有不透明的Views快鱼,你應(yīng)該設(shè)置它們的opaque屬性為YES。原因是這會使系統(tǒng)用一個最優(yōu)的方式渲染這些views纲岭。這個簡單的屬性在IB或者代碼里都可以設(shè)定抹竹。
Apple的文檔對于為圖片設(shè)置不透明屬性的描述是:
(opaque)這個屬性給渲染系統(tǒng)提供了一個如何處理這個view的提示。如果設(shè)為YES止潮, 渲染系統(tǒng)就認(rèn)為這個view是完全不透明的窃判,這使得渲染系統(tǒng)優(yōu)化一些渲染過程和提高性能。如果設(shè)置為NO喇闸,渲染系統(tǒng)正常地和其它內(nèi)容組成這個View袄琳。默認(rèn)值是YES询件。
在相對比較靜止的畫面中,設(shè)置這個屬性不會有太大影響唆樊。然而當(dāng)這個view嵌在scroll view里邊宛琅,或者是一個復(fù)雜動畫的一部分,不設(shè)置這個屬性的話會在很大程度上影響app的性能窗轩。
你可以在模擬器中用Debug\Color Blended Layers選項來發(fā)現(xiàn)哪些view沒有被設(shè)置為opaque夯秃。目標(biāo)就是,能設(shè)為opaque的就全設(shè)為opaque!
5.避免過于龐大的XIB
當(dāng)你加載一個XIB的時候所有內(nèi)容都被放在了內(nèi)存里痢艺,包括任何圖片仓洼。如果有一個不會即刻用到的view,你這就是在浪費(fèi)寶貴的內(nèi)存資源了堤舒。
如果你不得不XIB的話色建,使他們盡量簡單。嘗試為每個Controller配置一個單獨(dú)的XIB舌缤,盡可能把一個View Controller的view層次結(jié)構(gòu)分散到單獨(dú)的XIB中去箕戳。
6.重用大開銷對象
一些objects的初始化很慢,比如NSDateFormatter和NSCalendar国撵。還需要注意的是陵吸,設(shè)置一個NSDateFormatter的速度差不多是和創(chuàng)建新的一樣慢的!官方建議緩存NSDateFormatter可以提高效率介牙。
NSDateFormatter優(yōu)化方法:
一. 延遲轉(zhuǎn)換
只在UI需要使用轉(zhuǎn)換結(jié)果時再進(jìn)行轉(zhuǎn)換壮虫。
二. 緩存到內(nèi)存
不同的iOS系統(tǒng)版本下,NSDateFormatter的線程安全性也不同环础,緩存的方式也有所區(qū)別囚似。iOS7之前,NSDateFormatter是非線程安全的线得,因此饶唤,多個線程訪問同一個NSDateFormatter對象,會導(dǎo)致APP崩潰贯钩。iOS7以及iOS7以后募狂,NSDateFormatter都是線程安全的,所以我們無需擔(dān)心NSDateFormatter對象使用過程中被另外一條線程修改角雷。
iOS7之前:
+(NSDateFormatter *)cachedDateFormatter {
NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary];
NSDateFormatter *dateFormatter = [threadDict objectForKey:@"cachedDateFormatter"];
if (!dateFormatter) {
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setLocale:[NSLocale currentLocale]];
[dateFormatter setDateFormat:@"YYYY-MM-dd HH:mm:ss"];
[threadDict setObject:dateFormatter forKey:@"cachedDateFormatter"];
}
return dateFormatter;
}
iOS7之后:(包括iOS7)
static NSDateFormatter *cachedDataFormatter = nil;
+(NSDateFormatter *)cachedDateFormatter {
if (!cachedDataFormatter) {
cachedDataFormatter = [[NSDateFormatter alloc] init];
[cachedDataFormatter setLocale:[NSLocale currentLocale]];
[cachedDataFormatter setDateFormat:@"YYYY-MM-dd HH:mm:ss"];
}
return cachedDataFormatter;
}
如果緩存了NSDateFormatter或者是其他依賴于currentLocale的對象祸穷,那么我們應(yīng)該監(jiān)聽NSCurrentLocaleDidChangeNotification通知,當(dāng)currentLocale變化時谓罗,及時更新被緩存的NSDateFormatter對象。
三. 利用C語言庫
如果日期格式是固定的季二,我們可以采用C語言中的strptime函數(shù)檩咱,這樣更加簡單高效揭措。
- (NSDate *)easyDateFormatter {
time_t t;
struct tm tm;
//ISO8601時間格式:2004-05-03T17:30:08+08:00
char *iso8601 = "2016-09-18";
strptime(iso8601, "%Y-%m-%d", &tm);
tm.tm_isdst = -1;
//tm結(jié)構(gòu)體中的tm.tm_hour為負(fù)數(shù),會導(dǎo)致mktime(&tm)計算錯誤
tm.tm_hour = 0;
t = mktime(&tm);
return [NSDate dateWithTimeIntervalSince1970:t+[[NSTimeZone localTimeZone] secondsFromGMT]];
}
7.UITableView的優(yōu)化
每個iOS開發(fā)者都會使用到UITableView刻蚯,它也是APP數(shù)據(jù)展示的一個非常常用而且重要的UI控件绊含,對它的性能優(yōu)化也是必不可少的。
- 通過正確的設(shè)置 reuseIdentifier 來重用 Cell炊汹。
- 盡量減少不必要的透明 View躬充。
- 盡量避免漸變效果、圖片拉伸和離屏渲染讨便。
- 當(dāng)不同的行的高度不一樣時充甚,盡量緩存它們的高度值。
- 如果Cell 展示的內(nèi)容來自網(wǎng)絡(luò)霸褒,確保用異步加載的方式來獲取數(shù)據(jù)伴找,并且緩存服務(wù)器的 response。
- 使用 shadowPath 來設(shè)置陰影效果废菱。
- 盡量減少 subview 的數(shù)量技矮,對于 subview 較多并且樣式多變的 Cell,可以考慮用異步繪制或重寫drawRect殊轴。
- 盡量優(yōu)化 - [UITableView tableView:cellForRowAtIndexPath:]
方法中的處理邏輯衰倦,如果確實(shí)要做一些處理,可以考慮做一次旁理,緩存結(jié)果樊零。 - 選擇合適的數(shù)據(jù)結(jié)構(gòu)來承載數(shù)據(jù),不同的數(shù)據(jù)結(jié)構(gòu)對不同操作的開銷是存在差異的韧拒。
- 緩存動態(tài)行高
8.不要在主線程處理耗時操作
這個不需要贅述了淹接,比較阻塞主線程導(dǎo)致頁面假死的情況我相信大家都遇到過,帶給使用者的用戶體驗?zāi)憧隙ǚ浅G宄?/p>
遇到耗時操作叛溢,我們可以使用Grand Central Dispatch塑悼,或者 NSOperation 和 NSOperationQueues單獨(dú)開辟線程去處理相關(guān)操作,需要更新UI的時候再切換到主線程中刷新UI楷掉。
GCD的模板:(短小精悍厢蒜,雖然我短,但是我能旋轉(zhuǎn)E胫病)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//todo something
dispatch_async(dispatch_get_main_queue(), ^{
// 切換到主線程刷新UI
});
});
當(dāng)然了斑鸦,多線程雖然雖然很好,但是增加了程序的復(fù)雜度和潛在風(fēng)險草雕,你需要考慮線程安全巷屿、線程依賴等相關(guān)問題。開辟多線程的同時也會花費(fèi)相應(yīng)資源墩虹。這個需要你綜合去評估了嘱巾!
9.繪制圖形
當(dāng)我們自定義圖形的時候憨琳,一般會選擇重寫View的drawRect方法。如果UIView檢測到-drawRect:方法被調(diào)用了旬昭,它就會為視圖分配一個寄宿圖篙螟,這個寄宿圖的像素尺寸等于視圖大小乘以contentsScale,一旦你實(shí)現(xiàn)了CALayerDelegate協(xié)議中的-drawLayer:inContext:方法或者UIView中的-drawRect:方法(其實(shí)就是前者的包裝方法)问拘,圖層就創(chuàng)建了一個繪制上下文遍略,這個上下文需要的內(nèi)存可從這個公式得出:圖層寬x圖層高x4字節(jié),寬高的單位均為像素骤坐。如果視圖的尺寸很大绪杏,可以想象這將會是一個多大的內(nèi)存開銷。
CAShapeLayer是一個通過矢量圖形而不是bitmap來繪制的圖層子類或油。用CGPath來定義想要繪制的圖形寞忿,CAShapeLayer會自動渲染。它可以完美替代我們的直接使用CoreGraphics繪制layer顶岸,對比之下CAShapeLayer有以下優(yōu)點(diǎn):
渲染快速腔彰。CAShapeLayer 使用了硬件加速,繪制同一圖形會比用 Core Graphics 快很多辖佣。
高效使用內(nèi)存霹抛。一個 CAShapeLayer 不需要像普通 CALayer 一樣創(chuàng)建一個寄宿圖形,所以無論有多大卷谈,都不會占用太多的內(nèi)存杯拐。
不會被圖層邊界剪裁掉。
不會出現(xiàn)像素化世蔗。
總結(jié)一下繪制性能優(yōu)化原則:
繪制圖形性能的優(yōu)化最好的辦法就是不去繪制端逼。
利用專有圖層代替繪圖需求。
不得不用到繪圖盡量縮小視圖面積污淋,并且盡量降低重繪頻率顶滩。
異步繪制,推測內(nèi)容寸爆,提前在其他線程繪制圖片礁鲁,在主線程中直接設(shè)置圖片。
10.圖形和動畫
圖形性能對用戶體驗有直接的影響赁豆,Instruments中的Core Animation工具用于測量物理機(jī)上的圖形性能仅醇,通過視圖的刷新頻率大小來判斷應(yīng)用的圖形性能。例如一個復(fù)雜的列表滾動時它的刷新率應(yīng)該努力趨近于 60fps才能讓用戶覺得夠流暢魔种,從這個數(shù)字也可以算出run loop最長的響應(yīng)時間應(yīng)該是16(1/60)毫秒析二。
啟動Instruments的Core Animation工具后可以發(fā)現(xiàn)左下部分有一堆選項,我們來逐個介紹:
1. Color Blended Layers
表示混合的圖層會為紅色,不透明的圖層為綠色节预,通常我們希望綠色的區(qū)域越多越好叶摄。Blended Layer是因為這些Layer是透明的漆改,系統(tǒng)在渲染這些view時需要將該view和下層view混合(Blend)后才能 計算出該像素點(diǎn)的實(shí)際顏色,如果這種blended layer很多准谚,那么在滾動列表時就甭想有流暢的效果。這也是盡量設(shè)置View為不透明的原因去扣。
解決blended layer問題也很簡單柱衔,檢查紅色區(qū)域view的opaque屬性,記得設(shè)置成YES愉棱。
2. Color Hits Green and Misses Red
設(shè)置layer的陰影(shadow)唆铐、圓角(cornerRadius)、遮罩(mask)奔滑、漸變(Gradient)等會讓其渲染的開銷很高艾岂,設(shè)置layer的shouldRasterize為YES,系統(tǒng)會將這些Layer緩存成Bitmap位圖供渲染使用朋其,如果失效時便丟棄這些Bitmap重新生成王浴。
使用這個選項后時,如果Rasterized的Layer失效梅猿,便會標(biāo)注為紅色氓辣,如果有效標(biāo)注為綠色。當(dāng)測試的應(yīng)用頻繁閃現(xiàn)出紅色標(biāo)注圖層時袱蚓,表明對圖層 做的Rasterization作用不大钞啸。
圖層Rasterization柵格化好處是對刷新率影響較小,壞處是刪格化處理后的Bitmap緩存需要占用內(nèi)存喇潘,而且當(dāng)圖層需要縮放時体斩,要對刪格 化后的Bitmap做額外計算。
3. Color Misaligned Images
Misaligned Image表示要繪制的點(diǎn)無法直接映射到頻幕上的像素點(diǎn)颖低,此時系統(tǒng)需要對相鄰的像素點(diǎn)做anti-aliasing反鋸齒計算絮吵,增加了圖形負(fù)擔(dān),通常這種問題出在對某些View的Frame重新計算和設(shè)置時產(chǎn)生的枫甲。
被縮放的圖片會被標(biāo)記為黃色,像素不對齊則會標(biāo)注為紫色源武。
上圖中被標(biāo)注為黃色的圖層,這是由于圖層顯示的是被縮放后的圖片想幻,如果這些圖片是通過網(wǎng)絡(luò)下載的粱栖,可以通過程序更新為確定的繪制大小來解決。還 有些系統(tǒng)Navigation Bar和Tool Bar的背景圖片使用的是拉伸(Streched)圖片脏毯,也會被表示為黃色闹究,這是屬于正常情況,通常無需修改食店。這種問題一般對性能影響不大渣淤,而是可能會在邊緣處虛化赏寇。
4. Color Offscreen-Rendered Yellow
Offscreen-Rendering離屏渲染意思是iOS要顯示一個視圖時,需要先在后臺用CPU計算出視圖的Bitmap价认,再交給GPU 做Onscreen-Rendering顯示在屏幕上嗅定,因為顯示一個視圖需要兩次計算,所以這種Offscreen-Rendering會導(dǎo)致app的圖 形性能下降用踩。
大部分Offscreen-Rendering都是和視圖Layer的Shadow和Mask相關(guān)渠退,下列情況會導(dǎo)致視圖的Offscreen- Rendering:
- 使用Core Graphics (CG開頭的類)。
- 使用drawRect()方法脐彩,即使為空碎乃。
- 將CALayer的屬性shouldRasterize設(shè)置為YES。
- 使用了CALayer的setMasksToBounds(masks)和setShadow*(shadow)方法以及設(shè)置cornerRadius(圓角), masks(遮罩), shadows(陰影),edge antialiasing(反鋸齒)等惠奸。
前兩種情況使用的是CPU離屏渲染梅誓,首先分配一塊內(nèi)存,然后進(jìn)行渲染操作生成一份bitmap位圖佛南,整個渲染過程會在你的應(yīng)用中同步的進(jìn)行梗掰,接著再將位圖打包發(fā)送到iOS里一個單獨(dú)的進(jìn)程--render server,理想情況下嗅回,render server將內(nèi)容交給GPU直接顯示到屏幕上愧怜。
offscreen-render對性能到底有什么影響?
通常大家說的離屏渲染指的是GPU這塊(當(dāng)然CPU這塊也會有影響妈拌,也需要消耗一定的資源)拥坛,比如修改了layer的陰影或者圓角,GPU需要做額外的渲染操作尘分。通常GPU在做渲染的時候是很快的猜惋,但是涉及到offscreen-render的時候情況就可能有些不同,因為需要額外開辟一個新的緩沖區(qū)進(jìn)行渲染培愁,然后繪制到當(dāng)前屏幕的過程需要做onscreen跟offscreen上下文之間的切換著摔,這個過程的消耗會比較昂貴,涉及到OpenGL的pipeline跟barrier定续,而且offscreen-render在每一幀都會涉及到谍咆,因此處理不當(dāng)肯定會對性能產(chǎn)生一定的影響,所以可以的話盡量減少offscreen-render的圖層
最后私股,最重要的是要學(xué)會如何使用Xcode集成的開發(fā)工具Instruments來具體分析項目的各個方面摹察,找到性能的瓶頸所在來做針對性的性能優(yōu)化。