上圖是幾種時(shí)間復(fù)雜度的關(guān)系玫荣,性能優(yōu)化一定程度上是為了降低程序執(zhí)行效率減低時(shí)間復(fù)雜度。 如下是幾種時(shí)間復(fù)雜度的實(shí)例:
O(1)
return array[index] == value;
O(n)
for (int i = 0, i < n, i++) {
if (array[i] == value)
return YES;
}
O(n2)
/// 找數(shù)組中重復(fù)的值
for (int i = 0, i < n, i++) {
for (int j = 0, j < n, j++) {
if (i != j && array[i] == array[j]) {
return YES;
}
}
}
1. OC 中幾種常見(jiàn)集合對(duì)象接口方法的時(shí)間復(fù)雜度
NSArray / NSMutableArray
-
containsObject; indexOfObject; removeObject
均會(huì)遍歷元素查看是否匹配计福,復(fù)雜度等于或小于 O(n) -
objectAtIndex概作;firstObject沪袭;lastObject; addObject; removeLastObject
這些只針對(duì)棧頂田度,棧底的操作時(shí)間復(fù)雜度都是 O(1) -
indexOfObject:inSortedRange:options:usingComparator:
使用的是二分查找妒御,時(shí)間復(fù)雜度是O(log n)
NSSet / NSMutableSet / NSCountedSet
集合類(lèi)型是無(wú)序并且沒(méi)有重復(fù)元素的。這樣可以使用hash table 進(jìn)行快速的操作镇饺。比如乎莉,addObject; removeObject; containsObject
都是按照 O(1) 來(lái)的。需要注意的是將數(shù)組轉(zhuǎn)成Set 時(shí)奸笤,會(huì)將重復(fù)元素合并為一個(gè)惋啃,并且失去排序。
NSDictionary / NSMutableDictionary
和 Set 一樣都可以使用 hash table 监右,多了鍵值對(duì)應(yīng)边灭。添加和刪除元素都是 O(1)。
containsObject
方法在數(shù)組和Set里的不同的實(shí)現(xiàn)
containsObject
在數(shù)組中的實(shí)現(xiàn)
///GUNSTEP NSArray indexOfObject: 方法的實(shí)現(xiàn)
- (BOOL)containsObject:(id)anObject {
return [self indexOfObject:anObject] != NSNotFound;
}
- (NSUInteger) indexOfObject: (id)anObject
{
unsigned c = [self count];
if (c > 0 && anObject != nil)
{
unsigned i;
IMP get = [self methodForSelector: oaiSel];
BOOL (*eq)(id, SEL, id)
= (BOOL (*)(id, SEL, id))[anObject methodForSelector: eqSel];
for (i = 0; i < c; i++)
if ((*eq)(anObject, eqSel, (*get)(self, oaiSel, i)) == YES)
return i;
}
return NSNotFound;
}
containsObject
在 Set 里的實(shí)現(xiàn):
- (BOOL) containsObject: (id)anObject
{
return (([self member: anObject]) ? YES : NO);
}
//在 GSSet,m 里有對(duì) member 的實(shí)現(xiàn)
- (id) member: (id)anObject
{
if (anObject != nil)
{
GSIMapNode node = GSIMapNodeForKey(&map, (GSIMapKey)anObject);
if (node != 0)
{
return node->key.obj;
}
}
return nil;
}
在數(shù)組中會(huì)遍歷所有元素查找到結(jié)果后返回秸侣,在Set中查找元素是通過(guò)鍵值的方式從map映射表中取出存筏,因?yàn)镾兒童里的元素是唯一的,所以可以hash元素對(duì)象作為key達(dá)到快速查找的目的味榛。
2. 使用GCD
進(jìn)行性能優(yōu)化
可以通過(guò)GCD提供的方法將一些耗時(shí)操作放到非主線(xiàn)程進(jìn)行,使得App 能夠運(yùn)行的更加流暢予跌,響應(yīng)更快搏色,但是使用GCD 時(shí)需要注意避免可能引起的線(xiàn)程爆炸和死鎖的情況。在非主線(xiàn)程處理任務(wù)也不是萬(wàn)能的券册,如果一個(gè)處理需要消耗大量?jī)?nèi)存或者大量CPU操作频轿,GCD也不合適垂涯,需要將大任務(wù)拆分成不同的階段任務(wù)分時(shí)間進(jìn)行處理。
避免線(xiàn)程爆炸的方法:
- 使用串行隊(duì)列
- 控制
NSOperationQueue
的并發(fā)數(shù) -NSOperationQueue.maxConcurrentOperationCount
舉個(gè)會(huì)造成線(xiàn)程爆炸和死鎖的例子:
for (int i = 0, i < 999; i++) {
dispatch_async(q,^{...});
}
dispatch_barrier_sync(q,^{...});
如何避免上述的的線(xiàn)程爆炸和死鎖呢航邢? 首先使用 dispatch_apply
dispatch_apply(999,q,^(size_t i){...});
或者使用 dispatch_semaphore
#define CONCURRENT_TASKS 4
dispatch_queue_t q = dispatch_queue_create("com.qiuxuewei.gcd", nil);
dispatch_semaphore_t sema = dispatch_semaphore_create(CONCURRENT_TASKS);
for (int i = 0; i < 999; i++) {
dispatch_async(q, ^{
dispatch_semaphore_signal(sema);
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
3. I/O 性能優(yōu)化
I/O 操作是性能消耗大戶(hù)耕赘,任何的I/O操作都會(huì)使低功耗狀態(tài)被打破。所以減少 I/O 操作次數(shù)是性能優(yōu)化關(guān)鍵膳殷。如下是優(yōu)化的一些方法:
- 將零碎的內(nèi)容作為一個(gè)整體進(jìn)行寫(xiě)入
- 使用合適的 I/O 操作 API
- 使用合適的線(xiàn)程
- 使用 NSCache 做緩存減少 I/O 次數(shù)
NSCache
為何使用 NSCache
而不適應(yīng) NSMutableDictionary
呢操骡?相交字典 NSCache
有以下優(yōu)點(diǎn):
- 自動(dòng)清理系統(tǒng)所占內(nèi)存(在接收到內(nèi)存警告??時(shí))
-
NSCache
是線(xiàn)程安全的 -
- (void)cache:(NSCache *)cache willEvictObject:(id)obj;
緩存對(duì)象在即將被清理時(shí)回調(diào)。 -
evictsObjectWithDiscardedContent
可以控制是否可被清理赚窃。
SDWebImage
在設(shè)置圖片時(shí)就使用 NSCache
進(jìn)行了性能優(yōu)化:
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
return [self.memCache objectForKey:key];
}
- (UIImage *)imageFromDiskCacheForKey:(NSString *)key {
// 檢查 NSCache 里是否有
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
return image;
}
// 從磁盤(pán)里讀
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
return diskImage;
}
利用 NSCache
自動(dòng)釋放內(nèi)存的特點(diǎn)將圖片放到 NSCache
里册招,這樣在內(nèi)存警告時(shí)會(huì)自動(dòng)清理掉不常用的圖片,在讀取 Cache 里內(nèi)容時(shí)勒极,如果沒(méi)有被清理直接返回圖片數(shù)據(jù)是掰,清理了會(huì)執(zhí)行 I/O 從磁盤(pán)中讀取圖片,通過(guò)這種方式減少磁盤(pán)操作辱匿,空間也會(huì)更加有效的控制釋放键痛。
4. 控制 App 的喚醒次數(shù)
通知,Voip, 定位匾七,藍(lán)牙 等都會(huì)使設(shè)備從 Standby 狀態(tài)喚起絮短。喚起這個(gè)過(guò)程會(huì)有比較大的消耗。應(yīng)該避免頻繁發(fā)生乐尊。 以 定位 API 舉例:
連續(xù)的位置更新
[locationManager startUpdatingLocation]
這個(gè)方法會(huì)使設(shè)備一直處于活躍狀態(tài)戚丸。
延時(shí)有效定位
[locationManager allowDeferredLocationUpdatesUntilTraveled:<#(CLLocationDistance)#> timeout:<#(NSTimeInterval)#>]
高效節(jié)能的定位方式,數(shù)據(jù)會(huì)緩存在位置硬件上扔嵌。適合跑步應(yīng)用限府。
重大位置變化
[locationManager startMonitoringSignificantLocationChanges]
會(huì)更節(jié)能,對(duì)于那些只有在位置有很大變化的時(shí)候才需要回調(diào)的應(yīng)用需要采用這種方式痢缎,比如天氣應(yīng)用胁勺。
區(qū)域監(jiān)測(cè)
[locationManager startMonitoringForRegion:<#(nonnull CLRegion *)#>]
也是一種節(jié)能的定位方式,比如在博物館內(nèi)按照不同區(qū)域監(jiān)測(cè)展示不同信息之類(lèi)的應(yīng)用独旷。
頻繁定位
// start monitoring location
[locationManager startUpdatingLocation]
// Stop monitoring when no longer needed
[locationManager stopUpdatingLocation]
不要輕易使用 startUpdatingLocation() 除非萬(wàn)不得已署穗,盡快的使用 stopUpdatingLocation() 來(lái)結(jié)束定位還用戶(hù)一個(gè)節(jié)能設(shè)備。
5. 預(yù)防性能問(wèn)題
堅(jiān)持幾個(gè)編碼原則:
- 優(yōu)化計(jì)算的復(fù)雜度從而減少CPU的使用
- 在應(yīng)用響應(yīng)交互的時(shí)候停止沒(méi)有必要的任務(wù)處理
- 設(shè)置合適的 Qos
- 將定時(shí)器任務(wù)合并嵌洼,讓CPU更多時(shí)候處于 idle 狀態(tài)
6. 性能優(yōu)化技巧篇
1. 復(fù)用機(jī)制
在 UICollectionView
和 UITableView
會(huì)使用到 代碼復(fù)用的機(jī)制案疲,在所展示的item數(shù)量超過(guò)屏幕所容納的范圍時(shí),只創(chuàng)建少量的條目(通常是屏幕最大容納量 + 1)麻养,通過(guò)復(fù)用來(lái)展示所有數(shù)據(jù)褐啡。這種機(jī)制不會(huì)為每一條數(shù)據(jù)都創(chuàng)建 Cell .增強(qiáng)效率和交互流暢性。 在iOS6以后鳖昌,不僅可以復(fù)用cell备畦,也可以復(fù)用每個(gè)section 的 header 和 footer低飒。 在復(fù)用UITableView 會(huì)用到的 API:
// 復(fù)用 Cell:
- [UITableView dequeueReusableCellWithIdentifier:];
- [UITableView registerNib:forCellReuseIdentifier:];
- [UITableView registerClass:forCellReuseIdentifier:];
- [UITableView dequeueReusableCellWithIdentifier:forIndexPath:];
// 復(fù)用 Section 的 Header/Footer:
- [UITableView registerNib:forHeaderFooterViewReuseIdentifier:];
- [UITableView registerClass:forHeaderFooterViewReuseIdentifier:];
- [UITableView dequeueReusableHeaderFooterViewWithIdentifier:];
在使用代碼復(fù)用需要注意在設(shè)置Cell 屬性是,條件判斷需要覆蓋所有可能懂盐,避免因?yàn)閺?fù)用導(dǎo)致數(shù)據(jù)錯(cuò)誤的問(wèn)題褥赊。例如在 cellForRowAtIndexPath:
方法內(nèi)部:
if (indexPath %2 == 0) {
cell.backgroundColor = [UIColor redColor];
}else{
cell.backgroundColor = [UIColor clearColor];
}
2. 設(shè)置View為不透明
UIView
又一個(gè) opaque
屬性, 在不需要透明效果的時(shí)候莉恼,應(yīng)該盡量設(shè)置它為 YES, 可以提高繪圖效率拌喉。 在靜態(tài)視圖作用可能不明顯,但在 UITableVeiw
或 UICollectionView
這種滾動(dòng) 的 Scroll View 或是一個(gè)復(fù)雜動(dòng)畫(huà)中类垫,透明效果對(duì)程序性能有較大的影響司光!
3. 避免使用臃腫的 Xib 文件
當(dāng)加載一個(gè) Xib 時(shí),它所有的內(nèi)容都會(huì)被加載悉患,如歌這個(gè) Xib 中有的View 你不會(huì)馬上用到残家,加載就是浪費(fèi)資源。而加載 StoryBoard 時(shí)售躁,并不會(huì)把所有的ViewController 都加載坞淮,只會(huì)按需加載。
4. 不要阻塞主線(xiàn)程
UIKit
會(huì)把它所有的工作放在主線(xiàn)程執(zhí)行陪捷,比如:繪制界面回窘,管理手勢(shì),響應(yīng)輸入等市袖。當(dāng)把所有代碼邏輯都放在主線(xiàn)程時(shí)啡直,有可能因?yàn)楹臅r(shí)太長(zhǎng)而卡住主線(xiàn)程造成程序無(wú)法響應(yīng),流暢性差等問(wèn)題苍碟。所以一些 I/O 操作酒觅,網(wǎng)絡(luò)數(shù)據(jù)解析都需要異步在非主線(xiàn)程處理。
5. 使用尺寸匹配的UIImage
當(dāng)從 App bundle 中加載圖片到 UIImageView 時(shí)微峰,最好確保圖片的尺寸和 UIImageView 相對(duì)應(yīng)舷丹。否則會(huì)使UIImageView 對(duì)圖片進(jìn)行拉伸,這樣會(huì)影響性能蜓肆。如果圖片時(shí)從網(wǎng)絡(luò)加載颜凯,需要手動(dòng)進(jìn)行 scale。在UIImageView 中使用resize 后的圖片
6. 選擇合適的容器
在使用 NSArray / NSDictionary / NSSet
時(shí)仗扬,了解他們的特點(diǎn)便于在合適的時(shí)機(jī)選擇他們症概。
- Array:數(shù)組。有序的早芭,通過(guò) index 查找很快穴豫,通過(guò) value 查找很慢,插入和刪除較慢逼友。
- Dictionary:字典精肃。存儲(chǔ)鍵值對(duì),通過(guò)鍵查找很快帜乞。
- Set:集合司抱。無(wú)序的,通過(guò) value 查找很快黎烈,插入和刪除較快习柠。
7. 啟用 GZIP 數(shù)據(jù)壓縮
在網(wǎng)絡(luò)請(qǐng)求的數(shù)據(jù)量較大時(shí)邦蜜,可以將數(shù)據(jù)進(jìn)行壓縮再進(jìn)行傳輸捉超。可以降低延遲鳖悠,縮短網(wǎng)絡(luò)交互時(shí)間烈炭。
8. 懶加載視圖 / 視圖隱藏
展現(xiàn)視圖的兩種形式一種是懶加載溶锭,當(dāng)用到的時(shí)候去創(chuàng)建并展現(xiàn)給用戶(hù),另外一種提前分配內(nèi)存創(chuàng)建出視圖符隙,不用的時(shí)候?qū)⑵潆[藏趴捅,等用到的時(shí)候?qū)⑵渫该鞫茸優(yōu)?,兩種方案各有利弊霹疫。懶加載更合理的使用內(nèi)存拱绑,視圖隱藏讓視圖的展現(xiàn)更迅速。在選擇時(shí)需要權(quán)衡兩者利弊做出最優(yōu)選擇丽蝎。
9. 緩存
開(kāi)發(fā)需要秉承一個(gè)原則猎拨,對(duì)于一些更新頻率低,訪(fǎng)問(wèn)頻率高的內(nèi)容進(jìn)行緩存屠阻,例如:
- 服務(wù)器響應(yīng)數(shù)據(jù)
- 圖片
- 計(jì)算值 (UITableView 的 row height)
10. 處理 Memory Warning
處理 Memory Warning 的幾種方式:
- 在 AppDelegate 中實(shí)現(xiàn)
- [AppDelegate applicationDidReceiveMemoryWarning:]
代理方法红省。 - 在
UIViewController
中重載didReceiveMemoryWarning
方法。 - 監(jiān)聽(tīng)
UIApplicationDidReceiveMemoryWarningNotification
通知栏笆。
當(dāng)通過(guò)這些方式監(jiān)聽(tīng)到內(nèi)存警告時(shí)类腮,你需要馬上釋放掉不需要的內(nèi)存從而避免程序被系統(tǒng)殺掉。
比如蛉加,在一個(gè) UIViewController 中蚜枢,你可以清除那些當(dāng)前不顯示的 View,同時(shí)可以清除這些 View 對(duì)應(yīng)的內(nèi)存中的數(shù)據(jù)针饥,而有圖片緩存機(jī)制的話(huà)也可以在這時(shí)候釋放掉不顯示在屏幕上的圖片資源厂抽。
但是需要注意的是,你這時(shí)清除的數(shù)據(jù)丁眼,必須是可以在重新獲取到的筷凤,否則可能因?yàn)楸匾獢?shù)據(jù)為空,造成程序出錯(cuò)。在開(kāi)發(fā)的時(shí)候藐守,可以使用 iOS Simulator 的 Simulate memory warning 的功能來(lái)測(cè)試你處理內(nèi)存警告的代碼挪丢。
11. 復(fù)用高開(kāi)銷(xiāo)對(duì)象
高開(kāi)銷(xiāo)對(duì)象,顧名思義就是初始化很耗性能的對(duì)象卢厂。比如:NSDateFormatter
, NSCalendar
.為了避免頻繁創(chuàng)建乾蓬,我們可以使用一個(gè)全局單例強(qiáng)引用著這個(gè)對(duì)象,保證整個(gè)App 的生命周期只被初始化一次慎恒。
// no property is required anymore. The following code goes inside the implementation (.m)
- (NSDateFormatter *)dateFormatter {
static NSDateFormatter *dateFormatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd a HH:mm:ss EEEE"];
});
return dateFormatter;
}
設(shè)置 NSDateFormatter 的 date format 跟創(chuàng)建一個(gè)新的 NSDateFormatter 對(duì)象一樣慢任内,因此當(dāng)你的程序中要用到多種格式的 date format,而每種又會(huì)用到多次的時(shí)候融柬,你可以嘗試為每種 date format 創(chuàng)建一個(gè)可復(fù)用的 NSDateFormatter 對(duì)象來(lái)提供程序的性能死嗦。
12. 選擇正確的網(wǎng)絡(luò)返回?cái)?shù)據(jù)格式
通常用到的有兩種: JSON 和 XML。 JSON 優(yōu)點(diǎn):
- 能夠更快的被解析
- 在承載相同數(shù)據(jù)時(shí)粒氧,體積比XML更小越除,傳輸?shù)臄?shù)據(jù)量更小。
缺點(diǎn):
- 需要整個(gè)JSON數(shù)據(jù)全部加載完成后才能開(kāi)始解析
而XML的優(yōu)缺點(diǎn)恰好相反靠欢。解析數(shù)據(jù)不需要全部讀取完才解析廊敌,可以變加載邊解析,這樣在處理大數(shù)據(jù)集時(shí)可以有效提高性能门怪。 選擇哪種格式取決于應(yīng)用場(chǎng)景骡澈。
13. 合理設(shè)置背景圖片
為一個(gè)View 設(shè)置背景圖,我們想到的方案有兩種
- 為視圖加一個(gè) UIImageView 設(shè)置 UIImage 作為背景
- 通過(guò)
[UIColor colorWithPatternImage:<#(nonnull UIImage *)#>]
將一張圖轉(zhuǎn)化為 UIColor, 直接為 View 設(shè)置 backgroundColor掷空。
兩種方案各有優(yōu)缺點(diǎn):若使用一個(gè)全尺寸圖片作為背景圖使用 UIImageView 會(huì)節(jié)省內(nèi)存肋殴。 當(dāng)你計(jì)劃采用一個(gè)小塊的模板樣式圖片,就像貼瓷磚那樣來(lái)重復(fù)填充整個(gè)背景時(shí)坦弟,你應(yīng)該用 [UIColor colorWithPatternImage:<#(nonnull UIImage *)#>]
這個(gè)方法护锤,因?yàn)檫@時(shí)它能夠繪制的更快,并且不會(huì)用到太多的內(nèi)存酿傍。
14. 減少離屏渲染
離屏渲染:GPU在當(dāng)前屏幕緩沖區(qū)以外新開(kāi)辟一個(gè)緩沖區(qū)進(jìn)行渲染操作烙懦。 離屏渲染需要多次切換上下文環(huán)境:先是從當(dāng)前屏幕(On-Screen)切換到離屏(Off-Screen);等到離屏渲染結(jié)束以后赤炒,將離屏緩沖區(qū)的渲染結(jié)果顯示到屏幕上又需要將上下文環(huán)境從離屏切換到當(dāng)前屏幕氯析,而上下文環(huán)境的切換是一項(xiàng)高開(kāi)銷(xiāo)的動(dòng)作。
設(shè)置如下屬性均會(huì)造成離屏渲染:
- shouldRasterize(光柵化)
- masks(遮罩)
- shadows(陰影)
- edge antialiasing(抗鋸齒)
- group opacity(不透明)
- 復(fù)雜形狀設(shè)置圓角等
- 漸變
例如給一個(gè)View設(shè)置陰影莺褒,通常我們會(huì)使用這種方式:
imageView.layer.shadowOffset = CGSizeMake(5.0f, 5.0f);
imageView.layer.shadowRadius = 5.0f;
imageView.layer.shadowOpacity = 0.6;
這種方式會(huì)觸發(fā)離屏渲染掩缓,造成不必要的內(nèi)存開(kāi)銷(xiāo),我們完全可以使用如下方式代替:
imageView.layer.shadowPath = [[UIBezierPath bezierPathWithRect:CGRectMake(imageView.bounds.origin.x+5, imageView.bounds.origin.y+5, imageView.bounds.size.width, imageView.bounds.size.height)] CGPath];
imageView.layer.shadowOpacity = 0.6;
不會(huì)造成離屏渲染遵岩。
15. 光柵化
CALayer 有一個(gè)屬性是 shouldRasterize 通過(guò)設(shè)置這個(gè)屬性為 YES 可以將圖層繪制到一個(gè)屏幕外的圖像你辣,然后這個(gè)圖像將會(huì)被緩存起來(lái)并繪制到實(shí)際圖層的 contents 和子圖層,如果很很多的子圖層或者有復(fù)雜的效果應(yīng)用,這樣做就會(huì)比重繪所有事務(wù)的所有幀來(lái)更加高效舍哄。但是光柵化原始圖像需要時(shí)間宴凉,而且會(huì)消耗額外的內(nèi)存。
cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [[UIScreen mainScreen] scale];
使用光柵化的一個(gè)前提是視圖不會(huì)頻繁變化蠢熄,若一個(gè)頻繁變化的視圖跪解,例如 排版多變,高度不同的 Cell, 光柵化的意義就不大了签孔,反而造成必要的內(nèi)存損耗。
16. 優(yōu)化 UITableView
- 通過(guò)正確的設(shè)置 reuseIdentifier 來(lái)重用 Cell窘行。
- 盡量減少不必要的透明 View饥追。
- 盡量避免漸變效果、圖片拉伸和離屏渲染罐盔。
- 當(dāng)不同的行的高度不一樣時(shí)但绕,盡量緩存它們的高度值。
- 如果 Cell 展示的內(nèi)容來(lái)自網(wǎng)絡(luò)惶看,確保用異步加載的方式來(lái)獲取數(shù)據(jù)捏顺,并且緩存服務(wù)器的 response。
- 使用 shadowPath 來(lái)設(shè)置陰影效果纬黎。
- 盡量減少 subview 的數(shù)量幅骄,對(duì)于 subview 較多并且樣式多變的 Cell,可以考慮用異步繪制或重寫(xiě) drawRect本今。
- 盡量?jī)?yōu)化 - [UITableView tableView:cellForRowAtIndexPath:] 方法中的處理邏輯拆座,如果確實(shí)要做一些處理,可以考慮做一次冠息,緩存結(jié)果挪凑。
- 選擇合適的數(shù)據(jù)結(jié)構(gòu)來(lái)承載數(shù)據(jù),不同的數(shù)據(jù)結(jié)構(gòu)對(duì)不同操作的開(kāi)銷(xiāo)是存在差異的逛艰。
- 對(duì)于 rowHeight躏碳、sectionFooterHeight、sectionHeaderHeight 盡量使用常量散怖。
17.選擇合適數(shù)據(jù)存儲(chǔ)方式
iOS 中數(shù)據(jù)存儲(chǔ)方案有以下幾種:
- NSUserDefaults菇绵。只適合用來(lái)存小數(shù)據(jù)。
- XML杭抠、JSON脸甘、Plist 等文件。JSON 和 XML 文件的差異在「選擇正確的數(shù)據(jù)格式」已經(jīng)說(shuō)過(guò)了偏灿。
- 使用 NSCoding 來(lái)存檔丹诀。NSCoding 同樣是對(duì)文件進(jìn)行讀寫(xiě),所以它也會(huì)面臨必須加載整個(gè)文件才能繼續(xù)的問(wèn)題。
- 使用 SQLite 數(shù)據(jù)庫(kù)铆遭∠踝可以配合 FMDB 使用。數(shù)據(jù)的相對(duì)文件來(lái)說(shuō)還是好處很多的枚荣,比如可以按需取數(shù)據(jù)碗脊、不用暴力查找等等。
- 使用 CoreData橄妆。 Apple 提供的對(duì)于SQLite 的封裝衙伶,性能不如使用原生 SQLite, 不推薦使用。
18. 減少應(yīng)用啟動(dòng)時(shí)間
在啟動(dòng)時(shí)的一些網(wǎng)絡(luò)配置害碾,數(shù)據(jù)庫(kù)配置矢劲,數(shù)據(jù)解析的工作放在異步線(xiàn)程進(jìn)行。
19. 使用 Autorelease Pool
當(dāng)需要在代碼中創(chuàng)建許多臨時(shí)對(duì)象時(shí)慌随,你會(huì)發(fā)現(xiàn)內(nèi)存消耗激增直到這些對(duì)象被釋放芬沉,一個(gè)問(wèn)題是這些內(nèi)存只會(huì)到 UIKit 銷(xiāo)毀了它對(duì)應(yīng)的 Autorelease Pool 后才會(huì)被釋放,這就意味著這些內(nèi)存不必要地會(huì)空占一些時(shí)間阁猜。這時(shí)候就是我們顯式的使用 Autorelease Pool 的時(shí)候了丸逸,一個(gè)示例如下:
//一個(gè)很大數(shù)組
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. */
}
}
添加 Autorelease Pool 會(huì)在每一次循環(huán)中釋放掉臨時(shí)對(duì)象,提高性能剃袍。
20. 合理選擇 imageNamed
和 imageWithContentsOfFile
-
imageNamed
會(huì)對(duì)圖片進(jìn)行緩存黄刚,適合多次使用某張圖片 -
imageWithContentsOfFile
從bundle中加載圖片文件,不會(huì)進(jìn)行緩存笛园,適用于加載一張較大的并且只使用一次的圖片隘击,例如引導(dǎo)圖等
今年的 WWDC 2018 Apple 向我們推薦了一種性能比較高的大圖加載方案:
func downsample(imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage {
let sourceOpt = [kCGImageSourceShouldCache : false] as CFDictionary
// 其他場(chǎng)景可以用createwithdata (data并未decode,所占內(nèi)存沒(méi)那么大),
let source = CGImageSourceCreateWithURL(imageURL as CFURL, sourceOpt)!
let maxDimension = max(pointSize.width, pointSize.height) * scale
let downsampleOpt = [kCGImageSourceCreateThumbnailFromImageAlways : true,
kCGImageSourceShouldCacheImmediately : true ,
kCGImageSourceCreateThumbnailWithTransform : true,
kCGImageSourceThumbnailMaxPixelSize : maxDimension] as CFDictionary
let downsampleImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOpt)!
return UIImage(cgImage: downsampleImage)
}
詳細(xì)關(guān)于兩者的分析可參照另外一篇博客:iOS-UIImage imageWithContentsOfFile 和 imageName 對(duì)比
21. 合理進(jìn)行線(xiàn)程分配
GCD 很輕易的可以開(kāi)辟一個(gè)異步線(xiàn)程(不會(huì)100%開(kāi)辟新線(xiàn)程),若不加以控制研铆,會(huì)導(dǎo)致開(kāi)辟的子線(xiàn)程越來(lái)越多浪費(fèi)內(nèi)存埋同。并且在多線(xiàn)程情況下因?yàn)榫W(wǎng)絡(luò)時(shí)序會(huì)造成數(shù)據(jù)處理錯(cuò)亂,所以可以:
- UI 操作和 DataSource 操作在主線(xiàn)程
- DB 操作棵红,日志記錄凶赁,網(wǎng)絡(luò)回調(diào)在各自固定線(xiàn)程
- 不同業(yè)務(wù),通過(guò)使用隊(duì)列保持?jǐn)?shù)據(jù)一致性逆甜。
22. 預(yù)處理和延時(shí)加載
預(yù)處理:初次展示需要消耗大量?jī)?nèi)存的數(shù)據(jù)需提前在后臺(tái)線(xiàn)程處理完畢虱肄,需要時(shí)將處理好的數(shù)據(jù)進(jìn)行展現(xiàn) 延時(shí)加載:提前加載下級(jí)界面的數(shù)據(jù)內(nèi)容。舉個(gè)栗子:類(lèi)似抖音視頻滑動(dòng)交煞,在播放當(dāng)前視頻的時(shí)候就提前將下個(gè)視頻的數(shù)據(jù)加載好咏窿,等滑到下個(gè)視頻時(shí)直接進(jìn)行展示!
23. 在合適的時(shí)機(jī)使用 CALayer 替代 UIView
若視圖無(wú)需和用戶(hù)交互素征,類(lèi)似繪制線(xiàn)條集嵌,單純展示一張圖片萝挤,可以將圖片對(duì)象賦值給 layer 的 content 屬性,以提高性能根欧。 但是不能濫用怜珍,否則會(huì)造成代碼難以維護(hù)的惡果。