iOS-性能優(yōu)化深入探究

上圖是幾種時(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ī)制

UICollectionViewUITableView會(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)視圖作用可能不明顯,但在 UITableVeiwUICollectionView這種滾動(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. 合理選擇 imageNamedimageWithContentsOfFile

  • 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ù)的惡果。

原文鏈接:https://juejin.im/post/5b3b41385188251abe49f6f9

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末凤粗,一起剝皮案震驚了整個(gè)濱河市酥泛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嫌拣,老刑警劉巖柔袁,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異亭罪,居然都是意外死亡瘦馍,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)应役,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人燥筷,你說(shuō)我怎么就攤上這事箩祥。” “怎么了肆氓?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵袍祖,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我谢揪,道長(zhǎng)蕉陋,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任拨扶,我火速辦了婚禮凳鬓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘患民。我一直安慰自己缩举,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布匹颤。 她就那樣靜靜地躺著仅孩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪印蓖。 梳的紋絲不亂的頭發(fā)上辽慕,一...
    開(kāi)封第一講書(shū)人閱讀 52,262評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音赦肃,去河邊找鬼溅蛉。 笑死公浪,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的温艇。 我是一名探鬼主播因悲,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼勺爱!你這毒婦竟也來(lái)了晃琳?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤琐鲁,失蹤者是張志新(化名)和其女友劉穎卫旱,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體围段,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡顾翼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了奈泪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片适贸。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖涝桅,靈堂內(nèi)的尸體忽然破棺而出拜姿,到底是詐尸還是另有隱情,我是刑警寧澤冯遂,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布蕊肥,位于F島的核電站,受9級(jí)特大地震影響蛤肌,放射性物質(zhì)發(fā)生泄漏壁却。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一裸准、第九天 我趴在偏房一處隱蔽的房頂上張望展东。 院中可真熱鬧,春花似錦狼速、人聲如沸琅锻。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)恼蓬。三九已至,卻和暖如春僵芹,著一層夾襖步出監(jiān)牢的瞬間处硬,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工拇派, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留荷辕,地道東北人凿跳。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像疮方,于是被迫代替她去往敵國(guó)和親控嗜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容