1、盡可能的使用reuseIdentifier
盡可能的使用系統已經實現的重用機制。如UITableViewCell、UICollectionViewCell、UITableViewHeaderFooterView等;
為了性能最優化,table view用 tableView:cellForRowAtIndexPath: 為rows分配cells的時候,它的數據應該重用自UITableViewCell。 一個table view維持一個隊列的數據可重用的UITableViewCell對象。不使用reuseIdentifier的話,每顯示一行table view就不得不設置全新的cell。這對性能的影響可是相當大的,尤其會使app的滾動體驗大打折扣。
2、盡量把view設置為完全不透明
opaque屬性盡可能設置為YES。
這個屬性給渲染系統提供了一個如何處理這個view的提示。如果設為YES, 渲染系統就認為這個view是完全不透明的,這使得渲染系統優化一些渲染過程和提高性能。如果設置為NO,渲染系統需要結合上層view的色值和透明度,更為復雜的計算當前view的顏色值等,復雜的計算和渲染必然導致性能的下降,尤其是滾動視圖的情況。
3、避免過于龐大的XIB
首先,xib的類被初始化,是作為nib文件被加載到內存中的,尤其是當XIB有引用大量圖片或音頻資源的情況下,大量文件被加載到內存中必然導致性能的下降。所以,一是盡可能避免XIB的使用,二是不可避免的情況下,盡可能減小XIB文件的大小或者說復雜度
4、不要阻塞主線程
UIKit層的所有任務一定在主線程,包括UI渲染,觸摸點擊等事件響應等,長時間的耗時操作,諸如網絡請求、本地數據庫操作等如果放在主線程,必然會導致UIKit層的響應變慢或者延遲,會直接影響到用戶的感受,所以耗時操作可另外開辟線程執行,如GCD和NSOperationQueue
5、圓角圖片
- 直接設置imageV.layer.cornerRadius和imageV.layer.masksToBounds = YES必然會導致離屏渲染,大量使用圓角圖片的前提下,這種方法不可取
- 使用CAShapeLayer和UIBezierPath設置圓角,在設置好image的imageView上繪制一層圓形遮罩,此方法類似設置masksToBounds = YES,不可取
UIImageView * imageview = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 150, 150)];
imageview.center = self.view.center;
imageview.image = [UIImage imageNamed:@"meinv"];
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageview.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:imageview.bounds.size];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc]init];
//設置大小
maskLayer.frame = imageview.bounds;
//設置圖形樣子
maskLayer.path = maskPath.CGPath;
imageview.layer.mask = maskLayer;
[self.view addSubview:imageview];
- 使用貝塞爾曲線UIBezierPath和Core Graphics框架畫出一個圓角圖片,可取,沒有離屏渲染
UIImageView * imageview = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 150, 150)];
imageview.center = self.view.center;
imageview.image = [UIImage imageNamed:@"meinv"];
//開始對imageView進行畫圖
UIGraphicsBeginImageContextWithOptions(imageview.bounds.size, TRUE, 0);
//使用貝塞爾曲線畫出一個圓形圖
[[UIBezierPath bezierPathWithOvalInRect:imageview.bounds] addClip];
[imageview drawRect:imageview.bounds];
imageview.image = UIGraphicsGetImageFromCurrentImageContext();
//結束畫圖
UIGraphicsEndImageContext();
[self.view addSubview:imageview];
6、重用和懶加載views
更多的view意味著更多的渲染,也就是更多的CPU和內存消耗,對于那種嵌套了很多view在UIScrollView里邊的app更是如此;
這里我們用到的技巧就是模仿UITableView和UICollectionView的操作: 不要一次創建所有的subview,而是當需要時才創建,當它們完成了使命,把他們放進一個可重用的隊列中。
7、正確使用緩存
原則:緩存所需要的,也就是那些不大可能改變但是需要經常讀取的東西。諸如,遠端服務器的響應,圖片,甚至計算結果,比如UITableView的行高。
為此,你可以借助工具或者第三方,避免不必要的多次請求和計算,如SDWebImage內部實現了圖片的緩存,UITableView-FDTemplateLayoutCell緩存UITableView的行高;
8、權衡渲染方法
對于簡單的視圖和動畫,通常我們使用UIView就可以完成,但是對于一些較為復雜的視圖和動畫,我們不得不動用Core Graphics層的繪圖和動畫機制。這個時候,我們就要根據情況,鑒于性能和效果兩個方面去考慮,找到更適合的處理方式
9、處理內存警告
一旦系統內存過低,iOS會通知所有運行中app。在官方文檔中是這樣記述:
如果你的app收到了內存警告,它就需要盡可能釋放更多的內存。最佳方式是移除對緩存,圖片object和其他一些可以重創建的objects的strong references。
幸運的是,UIKit提供了幾種收集低內存警告的方法:
1、在app delegate中使用applicationDidReceiveMemoryWarning: 的方法
2、在你的自定義UIViewController的子類(subclass)中覆蓋didReceiveMemoryWarning
3、注冊并接收 UIApplicationDidReceiveMemoryWarningNotification 的通知
一旦收到這類通知,你就需要釋放任何不必要的內存使用。
例如,UIViewController的默認行為是移除一些不可見的view, 它的一些子類則可以補充這個方法,刪掉一些額外的數據結構。一個有圖片緩存的app可以移除不在屏幕上顯示的圖片。
這樣對內存警報的處理是很必要的,若不重視,你的app就可能被系統殺掉。
然而,當你一定要確認你所選擇的object是可以被重現創建的來釋放內存。一定要在開發中用模擬器中的內存提醒模擬去測試一下。
10、重用大開銷對象和重復使用的對象
對于一些初始化很慢,而且又經常使用的對象,最好設置成類的屬性或者封裝成單例(整個app只會創建一次),比如NSDateFormatter和NSCalendar,百度地圖中常用的定位BMKLocationService對象
下面,使用延遲加載來實現重用
@property (nonatomic, strong)NSDateFormatter *dateFormat;
//在第一次使用的時候才會創建,后來再使用就用第一次創建好的對象,不會多次創建
-(NSDateFormatter *)dateFormat{
if (!_dateFormat) {
_dateFormat = [[NSDateFormatter alloc]init];
_dateFormat.dateFormat = @"YYYY-MM-DD HH:mm:ss";
}
return _dateFormat
}
11、盡量使用WebKit
UIWebView是基于移動版的Safari的,所以它的性能表現十分有限。特別是在對幾乎每個Web應用都會使用的JavaScript,表現的尤為糟糕。而iOS8引入的WebKit框架使得開發者可以在原生App中使用Nitro來提高網頁的性能和表現,Nitro就是Safari的JavaScript引擎。WKWebView保證在滑動時保持60的幀率,同時具有KVO,內建手勢,以及在App和網頁之間的原生交流方式。
12、選擇正確的數據存儲選項
iOS的本地數據存儲方式有:
- NSUserDefault
- XML, JSON, 或者 plist
- NSCoding歸檔
- SQLite
- Core Data
首先,NSUserDefault很好用,但只適用于小數據,類似簡易的字段值;
XML和JSON這種結構化歸檔,還需要把數據讀到內存中去解析,NSCoding也需要歸檔和解檔配合解析數據,中間過程過于復雜,特別是大數據的情況就更不適合了。
而SQLite和CoreData這種都能實現數據庫式的存儲,只要通過特定的語句就能實現數據的讀寫,唯一不同的是CoreData存儲過程中多了一層映射,可以直接操作Object,而SQLite操作的是單個的原始數據,而實際存儲后的結果都是DBMS
13、加速啟動時間
從點擊AppIcon啟動應用,到第一個畫面出現,應用的啟動時間,直接影響用戶對一款應用的判斷和使用體驗。關于啟動時間和如何優化
簡而言之:t(App總啟動時間) = t1(main()之前的加載時間) + t2(main()之后的加載時間)。
對于main()調用之前的耗時我們可以優化的點有:
1、減少不必要的framework,因為動態鏈接比較耗時
2、check framework應當設為optional和required,如果該framework在當前App支持的所有iOS系統版本都存在,那么就設為required,否則就設為optional,因為optional會有些額外的檢查
3、合并或者刪減一些OC類,關于清理項目中沒用到的類,使用工具AppCode代碼檢查功能
4、刪減一些無用的靜態變量
5、刪減沒有被調用到或者已經廢棄的方法。方法見:
http://stackoverflow.com/questions/35233564/how-to-find-unused-code-in-xcode-7 https://developer.Apple.com/library/ios/documentation/ToolsLanguages/Conceptual/Xcode_Overview/CheckingCodeCoverage.html
6、將不必須在+load方法中做的事情延遲到+initialize中
7、盡量不要用C++虛函數(創建虛函數表有開銷)main()調用之后的加載時間
在main()被調用之后,App的主要工作就是初始化必要的服務,顯示首頁內容等。App通常在AppDelegate類中的- (BOOL)Application:(UIApplication )Application didFinishLaunchingWithOptions:(NSDictionary )launchOptions方法中創建首頁需要展示的view,然后在當前runloop的末尾,主動調用CA::Transaction::commit完成視圖的渲染。而視圖的渲染主要涉及三個階段:
** 準備階段 這里主要是圖片的解碼
** 布局階段 首頁所有UIView的- (void)layoutSubViews()運行
** 繪制階段 首頁所有UIView的- (void)drawRect:(CGRect)rect運行
再加上啟動之后必要服務的啟動、必要數據的創建和讀取,這些就是我們可以嘗試優化的地方
因此,對于main()函數調用之后我們可以優化的點有:
1、不使用xib,直接視用代碼加載首頁視圖
2、NSUserDefaults實際上是在Library文件夾下會生產一個plist文件,如果文件太大的話一次能讀取到內存中可能很耗時,這個影響需要評估,如果耗時很大的話需要拆分(需考慮老版本覆蓋安裝兼容問題)
3、每次用NSLog方式打印會隱式的創建一個Calendar,因此需要刪減啟動時各業務方打的log,或者僅僅針對內測版輸出log
4、梳理應用啟動時發送的所有網絡請求,是否可以統一在異步線程請求
14、使用Autorelease Pool
NSAutoreleasePool負責釋放block中的autoreleased objects。一般情況下它會自動被UIKit調用。但是有些狀況下你也需要手動去創建它。
當在項目中處理數據等需要很多臨時變量的時候,你會發現內存持續增長,只有當這些臨時變量release的時候,內存才有所緩解;我們知道每創建一個對象,他會被添加到距離最近的Autoreleasepool中,所以,我們可以自己創建AutoreleasePool,使這些臨時變量可以盡早被釋放。
//每次遍歷autorelease pool都會釋放里面的變量
NSArray *urls = <# An array of file URLs #>;
for(NSURL *url in urls) {
@autoreleasepool {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding error:&error];
/* Process the string, creating and autoreleasing more objects. */
}
}
15、用正確的方式加載圖片
常見的從bundle中加載圖片的方式有兩種,一個是用imageNamed,二是用imageWithContentsOfFile,第一種比較常見一點。
既然有兩種類似的方法來實現相同的目的,那么他們之間的差別是什么呢?
imageNamed的優點是當加載時會緩存圖片。imageNamed的文檔中這么說:
這個方法用一個指定的名字在系統緩存中查找并返回一個圖片對象如果它存在的話。如果緩存中沒有找到相應的圖片,這個方法從指定的文檔中加載然后緩存并返回這個對象。
相反的,imageWithContentsOfFile僅加載圖片。
下面的代碼說明了這兩種方法的用法:
UIImage *img = [UIImage imageNamed:@"myImage"];// caching
UIImage *img = [UIImage imageWithContentsOfFile:@"myImage"];// no caching
那么,對于占用內存大且一次性使用的,就使用不會緩存的imageWithContentsOfFile,常用的小圖片或者icon就用會緩存的imageNamed;
16、盡可能避免日期格式轉換
NSDateFormatter的創建和設置都是很耗時的操作,用日期字符串轉日期的情況也很常出現。提高效率的其中一個方法是重用NSDateFormatter對象,那如果需要不同樣式的NSDateFormatter呢?
還有一個方法,盡量使用Unix時間戳,NSDate有直接從時間戳轉換為日期的方法
- (NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp {
return [NSDate dateWithTimeIntervalSince1970:timestamp];
}
17、tableView的優化系列
1、行高rowHeight的緩存UITableView+FDTemplateLayoutCell
2、有圖片的cell復用時,加載圖片的方式做優化,例如圓角處理,較大的圖片滑動過程中的加載方式 cell圖片加載優化
3、Color Blend Layers(圖層渲染,避免復合圖層和圖層透明) + Color Hits Green and Misses Red(光柵化shouldRasterize = YES, 預先渲染成位圖并緩存,導致離屏渲染) + Color Misaligned Images(避免圖片縮放) + Color OffScreen-Rendered Yellow(離屏渲染,圓角maskToBound = YES導致,合理處理圓角)
4、參考文章 UIKit優化
18、能用CAShapeLayer實現的就不要用drawRect重繪
原因分析: http://blog.csdn.net/sandyloo/article/details/51063799
- 渲染快速。CAShapeLayer使用了硬件加速,繪制同一圖形會比用Core Graphics快很多。
- 高效使用內存。一個CAShapeLayer不需要像普通CALayer一樣創建一個寄宿圖形,所以無論有多大,都不會占用太多的內存。
- 不會被圖層邊界剪裁掉。
- 不會出現像素化。
未完待續。。。。。。