OS X和iOS 內(nèi)存優(yōu)化

基礎(chǔ)優(yōu)化策略

  • 延遲分配&懶分配
MyGlobalInfo* GetGlobalBuffer() {
    static MyGlobalInfo* sGlobalBuffer = NULL;
    if ( sGlobalBuffer == NULL ) {
            sGlobalBuffer = malloc( sizeof( MyGlobalInfo ) );
     }
     return sGlobalBuffer;
}
  • 高效初始化內(nèi)存
    malloc分配的小塊內(nèi)存较屿,并不會(huì)保證清零初始化呀洲,一般會(huì)配上memset來(lái)初始化瞳筏。但memset會(huì)強(qiáng)制將虛擬內(nèi)存映射到觸發(fā)物理內(nèi)存舌涨,如果短時(shí)間內(nèi)并不需要寫(xiě)入數(shù)據(jù)蛛株,會(huì)額外增加內(nèi)存開(kāi)銷(xiāo)蜘犁。而calloc會(huì)保留要分配的虛擬地址空間翰苫,直到真正使用的時(shí)候才會(huì)映射到物理內(nèi)存并清零初始化,而且只是初始化要用到的內(nèi)存頁(yè)这橙。所以建議使用calloc代替malloc+memset

  • 復(fù)用頻繁使用的大內(nèi)存
    如果你的計(jì)算需要頻繁創(chuàng)建大的臨時(shí)buffer奏窑,可以考慮復(fù)用buffer而不是每次重新分配。即使每次使用的buffer大小可能不一樣屈扎,也可以通過(guò)realloc方法來(lái)擴(kuò)展已有Buffer埃唯。多線程環(huán)境下,最好將Buffer放入線程私有存儲(chǔ)里thread-local storage鹰晨,避免多個(gè)線程同時(shí)操作同一Buffer墨叛。
    緩存了Buffer減少了內(nèi)存分配的次數(shù),但也可能造成Footprint長(zhǎng)期較大模蜡,所以?xún)H適合需頻繁分配Buffer的場(chǎng)景漠趁。

  • 及時(shí)釋放無(wú)用的內(nèi)存
    及時(shí)釋放無(wú)用的內(nèi)存,尤其要清理內(nèi)存泄露問(wèn)題哩牍。

  • 小內(nèi)存分配
    malloc分配內(nèi)存塊的最小粒度為16字節(jié)棚潦,舉例,當(dāng)你需要分配4字節(jié)時(shí),malloc返回的是16字節(jié)的內(nèi)存塊辟汰;當(dāng)你需要24字節(jié)時(shí),返回的是32字節(jié)的內(nèi)存塊(16的整數(shù)倍)失尖。所以我們?cè)O(shè)計(jì)數(shù)據(jù)結(jié)構(gòu)時(shí)盡量占用16字節(jié)的整數(shù)倍妹窖。

  • 大內(nèi)存分配
    malloc分配大內(nèi)存時(shí)(包含多個(gè)內(nèi)存頁(yè))纬朝,會(huì)自動(dòng)使用vm_allocate來(lái)獲取內(nèi)存,而該過(guò)程只是分配了虛擬地址空間骄呼,并未立即分配對(duì)應(yīng)的物理內(nèi)存共苛。當(dāng)代碼想要讀寫(xiě)該內(nèi)存區(qū)域某個(gè)地址時(shí),會(huì)觸發(fā)缺頁(yè)錯(cuò)誤蜓萄,此時(shí)內(nèi)核會(huì)進(jìn)行以下操作:

  1. 從可用頁(yè)(free list)獲取一頁(yè)隅茎,并清零初始化。
  2. 將該物理頁(yè)記錄到VM Object的resident pages中嫉沽。
  3. 通過(guò)修改叫做pmap的結(jié)構(gòu)體, 將虛擬頁(yè)映射到物理頁(yè)辟犀。(pmap包含了CPU/MMU用來(lái)映射地址的頁(yè)表)

內(nèi)存頁(yè)的最小粒度為4K或16K,所以盡量分配其整數(shù)倍大小绸硕,避免浪費(fèi)內(nèi)存堂竟。

  • 批量分配
    如果需要分配多個(gè)同等大小的內(nèi)存塊,可以使用malloc_zone_batch_malloc 玻佩,它比多次調(diào)用malloc要高效的多出嘹,尤其當(dāng)內(nèi)存塊較小時(shí)(<4K)。這個(gè)方法會(huì)盡力分配請(qǐng)求的塊數(shù)咬崔,但最終返回的塊數(shù)可能少于請(qǐng)求的税稼,所以需要仔細(xì)判斷返回結(jié)果。

  • 批量釋放
    所有內(nèi)存分配都是在某個(gè)zone范圍內(nèi)垮斯,zone可以理解為一段可變大小的虛擬內(nèi)存娶聘。你可以在zone里分配多個(gè)內(nèi)存塊,然后一次性釋放整個(gè)zone甚脉,比單獨(dú)釋放每個(gè)內(nèi)存塊要高效的多丸升。

  • 延遲拷貝
    通過(guò)memcpymemmove 拷貝內(nèi)存叫做即時(shí)拷貝,源內(nèi)存塊和目標(biāo)內(nèi)存塊需要同時(shí)存在內(nèi)存中牺氨。當(dāng)拷貝較大內(nèi)存塊時(shí)狡耻,增加了應(yīng)用的整體內(nèi)存占用和內(nèi)存換出的幾率。
    如果拷貝完內(nèi)存后并不需要里面使用猴凹,可以使用vm_copy實(shí)現(xiàn)延遲拷貝夷狰。vm_copy并不創(chuàng)建真實(shí)的內(nèi)存塊,而是通過(guò)修改虛擬內(nèi)存映射郊霎,來(lái)表示目標(biāo)內(nèi)存區(qū)域是源內(nèi)存區(qū)域的一個(gè)寫(xiě)時(shí)復(fù)制版本沼头。為了實(shí)現(xiàn)延遲拷貝,內(nèi)核需要將源內(nèi)存頁(yè)從虛擬內(nèi)存空間中清理掉(物理內(nèi)存還在)。下一次進(jìn)程再訪問(wèn)源內(nèi)存頁(yè)時(shí)进倍,會(huì)觸發(fā)soft fault土至,內(nèi)核會(huì)將該內(nèi)存頁(yè)重新映射回虛擬內(nèi)存。處理soft fault跟即時(shí)拷貝性能損耗差不多猾昆,所以只在發(fā)生拷貝后長(zhǎng)時(shí)間不再訪問(wèn)數(shù)據(jù)的情況下優(yōu)勢(shì)明顯陶因。

  • iOS低內(nèi)存告警
    iOS的虛擬內(nèi)存沒(méi)有換出磁盤(pán)的機(jī)制,所以需要依賴(lài)應(yīng)用去釋放內(nèi)存垂蜗。當(dāng)iOS的可用內(nèi)存頁(yè)少于某閾值后楷扬,會(huì)嘗試釋放未修改的內(nèi)存頁(yè)(Clean Memory),如果需要的話(huà)也會(huì)終結(jié)一些切換到后臺(tái)的應(yīng)用贴见。過(guò)程中可能也會(huì)向運(yùn)行中的應(yīng)用發(fā)送低內(nèi)存告警通知烘苹。應(yīng)用收到該通知時(shí),需要盡可能清理不必要的內(nèi)存片部。比如根頁(yè)面為UITabViewCtroller的應(yīng)用螟加,可以先移除未展示的Tab, 下次需要展示時(shí)再重新加載。
    清理緩存數(shù)據(jù)時(shí)要注意 Memory Compression 帶來(lái)的影響吞琐,避免清理已經(jīng)壓縮過(guò)的數(shù)據(jù)時(shí)觸發(fā)了解壓操作,反而增加了內(nèi)存然爆。建議用 NSCache 代替 NSDictionary站粟,使用 NSPurgableData 代替 NSData

NSCache 分配的內(nèi)存實(shí)際上是 Purgeable Memory曾雕,可以由系統(tǒng)自動(dòng)釋放奴烙。NSCache 與 NSPureableData 的結(jié)合使用既能讓系統(tǒng)根據(jù)情況回收內(nèi)存,也可以在內(nèi)存清理的同時(shí)移除相關(guān)對(duì)象剖张。

針對(duì)具體內(nèi)存Region的優(yōu)化

IOKit

這部分主要是圖片切诀、OpenGL紋理、CVPixelBuffer等搔弄,比如通常是OpenGL的紋理幅虑,glTexImage2d調(diào)用產(chǎn)生的。iOS系統(tǒng)有相關(guān)釋放接口顾犹,但可能釋放不及時(shí)倒庵。

顯存可能被映射到某塊虛擬內(nèi)存,因此可以通過(guò)IOKit來(lái)查看紋理增長(zhǎng)情況炫刷。iOS的顯存就是內(nèi)存擎宝,而OSX才區(qū)分顯存和內(nèi)存。"

Graphics Memory (VRAM)
? iOS uses a Unified Memory Architecture — GPU and CPU share Physical Memory
? Graphics Driver allocates Virtual Memory for its resources
? Most of this is Resident and Dirty

紋理是在內(nèi)核態(tài)分配的浑玛,不計(jì)算到Allocations里邊绍申,但是也記為Dirty Size。創(chuàng)建一定數(shù)量紋理后,到達(dá)極限值极阅,則之后創(chuàng)建紋理就會(huì)失敗胃碾,App可能不會(huì)崩潰,但是出現(xiàn)異常涂屁,花屏书在,或者拍后頁(yè)白屏。

通常情況下拆又,開(kāi)發(fā)者已經(jīng)正確調(diào)用了釋放內(nèi)存的操作儒旬,但是OpenGL驅(qū)動(dòng)自己做了優(yōu)化,使得內(nèi)存并未真正地及時(shí)釋放掉帖族,僅僅是為了重用栈源。

Some drivers may keep the storage allocated so that they can reuse it for satisfying future allocations (rather than having to allocate new storage – a common misunderstanding this behaviour leads to is people thinking they have a memory leak), other drivers may not.

VM:ImageIO_IOSurface_Data

典型堆棧:

VM:ImageIO_PNG_Data

典型堆棧

UIImage的imageNamed:方法會(huì)將圖片數(shù)據(jù)緩存在內(nèi)存中。而imageWithContentsOfFile:方法則不會(huì)進(jìn)行緩存竖般,用完立即釋放掉了甚垦。優(yōu)化建議:

  1. 對(duì)于經(jīng)常需要使用的小圖,可以放到Assets.xcassets中涣雕,使用imageNamed:方法艰亮。
  2. 對(duì)于不經(jīng)常使用的大圖,不要放到Assets.xcassets中挣郭,且使用imageWithContentsOfFile:方法迄埃。

如果對(duì)于多圖的滾動(dòng)視圖,渲染到imageView中后兑障,可以使用autoreleasepool來(lái)盡早釋放:

for (int i=0;i<10;i++) {
    UIImageView *imageView = xxx;
    NSString *imageFile = xxx;
    @autoreleasepool {
        imageView.image = [UIImage imageWithContentsOfFile:imageFile];
    }
    [self.scrollView addSubview:imageView];
}

VM:Image IO

典型堆棧:

VM:IOAccelerator

典型堆棧

VM:CG raster data

典型堆棧:

光柵數(shù)據(jù)侄非,即為UIImage的解碼數(shù)據(jù)。SDWebImage將解碼數(shù)據(jù)做了緩存流译,避免渲染時(shí)候在主線程解碼而造成阻塞逞怨。

優(yōu)化措施:

[[SDImageCache sharedImageCache] setShouldDecompressImages:NO];
[[SDWebImageDownloader sharedDownloader] setShouldDecompressImages:NO];
[[SDImageCache sharedImageCache] setShouldCacheImagesInMemory:NO];

VM:CoreAnimation

典型堆棧:

mach_vm_allocate
vm_allocate
CA::Render::Shmem::new_shmem
CA::Render::Shmem::new_bitmap
CABackingStorePrepareUpdates_
CABackingStoreUpdate_
invocation function for block in CA::Layer::display_()
x_blame_allocations
[CALayer _display]
CA::Context::commit_transaction
CA::Transaction::commit()
[UIApplication _firstCommitBlock] _block_invoke_2
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
__CFRunLoopDoBlocks
__CFRunLoopRun
CFRunLoopRunSpecific
GSEventRunModal
UIApplicationMain
main
start

UIKit渲染數(shù)據(jù),大小跟UIView/CALayer尺寸有關(guān)福澡。
優(yōu)化措施:不要用太大的UIView和CALayer叠赦。

VM: CoreUI image data

典型堆棧

image

VM_ALLOCATE

這部分基本是對(duì)開(kāi)發(fā)者自行分配的大內(nèi)存進(jìn)行檢查。

__TEXT

優(yōu)化措施:清理冗余代碼革砸,縮小代碼段體積眯搭。

__DATA

可執(zhí)行二進(jìn)制的可寫(xiě)入靜態(tài)區(qū),主要包含

  • 非const的static變量
  • 全局變量

針對(duì)使用場(chǎng)景的優(yōu)化措施

圖像優(yōu)化

圖片占用的內(nèi)存大小實(shí)際與其分辨率相關(guān)的业岁,如果一個(gè)像素點(diǎn)占用4個(gè)byte的話(huà)鳞仙,width * height * 4 / 1024 / 1024 MB。

參考:WWDC 2018 Session 219:Image and Graphics Best Practices笔时。

imageNamed和imageWithContentsOfFile

  1. UIImage的imageNamed:方法會(huì)將圖片數(shù)據(jù)緩存在內(nèi)存中棍好,緩存使用的時(shí)NSCache,收到內(nèi)存警告會(huì)釋放。
  2. 而imageWithContentsOfFile:方法則不會(huì)進(jìn)行緩存借笙,不需要的時(shí)候就立即釋放掉了扒怖。

所以建議:

  1. 對(duì)于頻繁使用的小圖,可以放到Assets.xcassets中业稼,使用imageNamed:方法盗痒。
  2. 對(duì)于不經(jīng)常使用的大圖,不要放到Assets.xcassets中低散,且使用imageWithContentsOfFile:方法俯邓。

UIImage的異步解碼和渲染

UIImage只有在屏幕上渲染(self.imageView.image = image)的時(shí)候,才去解碼的熔号,解碼操作在主線程執(zhí)行稽鞭。所以,如果有非常多(如滑動(dòng)界面下載大量網(wǎng)絡(luò)圖片)或者較大圖片的解碼渲染操作引镊,則會(huì)阻塞主線程朦蕴。

可以通過(guò)如下方式,避免圖片使用時(shí)候的一些阻塞弟头、資源消耗過(guò)大吩抓、頻繁解碼等的情況。

  1. 異步下載網(wǎng)絡(luò)圖片赴恨,進(jìn)行內(nèi)存和磁盤(pán)緩存
  2. 對(duì)圖片進(jìn)行異步解碼疹娶,將解碼后的數(shù)據(jù)放到內(nèi)存緩存
  3. 主線程進(jìn)行圖片的渲染

異步解碼的詳細(xì)實(shí)現(xiàn),可以查看SDWebImage的SDImageCoderHelper.m文件嘱支。

適當(dāng)使用autoreleasepool

如果對(duì)于多圖的滾動(dòng)視圖,渲染到imageView中后挣饥,可以使用autoreleasepool來(lái)盡早釋放:

for (int i=0;i<10;i++) {
    UIImageView *imageView = xxx;
    NSString *imageFile = xxx;
    @autoreleasepool {
        imageView.image = [UIImage imageWithContentsOfFile:imageFile];
    }
    [self.scrollView addSubview:imageView];
}
復(fù)制代碼

UIGraphicsImageRenderer

建議使用iOS 10之后的UIGraphicsImageRenderer來(lái)執(zhí)行繪制任務(wù)除师。該API在iOS 12中會(huì)根據(jù)場(chǎng)景自動(dòng)選擇最合適的渲染格式,更合理地使用內(nèi)存扔枫。

另一個(gè)方式汛聚,采用UIGraphicsBeginImageContextWithOptionsUIGraphicsGetImageFromCurrentImageContext得到的圖片,每個(gè)像素點(diǎn)都需要4個(gè)byte短荐∫幸ǎ可能會(huì)有較大內(nèi)存空間上的浪費(fèi)。

Downsampling

對(duì)于一些場(chǎng)景忍宋,如UIImageView尺寸較小痕貌,而UIImage較大時(shí),直接展示原圖糠排,會(huì)有不必要的內(nèi)存和CPU消耗舵稠。

  • 之前的方式

將大圖縮小的時(shí)候,即downsampling的過(guò)程,一般需要將原始大圖加載到內(nèi)存哺徊,然后做一些坐標(biāo)空間的轉(zhuǎn)換室琢,再生成小圖。此過(guò)程中落追,如果使用UIGraphicsImageRenderer的繪制操作盈滴,會(huì)消耗比較多的資源。

UIImage *scaledImage = [self scaleImage:image newSize:CGSizeMake(2048, 2048)];

- (UIImage *)scaleImage:(UIImage *)image newSize:(CGSize)newSize {
    // 這一步只是根據(jù)size創(chuàng)建一個(gè)bitmap的上下文轿钠,參數(shù)scale比較關(guān)鍵巢钓。
    UIGraphicsBeginImageContextWithOptions(newSize, NO, 1); 
    [image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)]; 
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); // 79.7
    UIGraphicsEndImageContext(); // 15.7MB
    return newImage;
}

UIGraphicsBeginImageContextWithOptions需要跟接收參數(shù)相關(guān)的context消耗,消耗的內(nèi)存與三個(gè)參數(shù)相關(guān)谣膳。其實(shí)不大竿报。

關(guān)鍵在于:UIImage的drawInRect:方法在繪制時(shí),會(huì)將圖片先解碼继谚,再生成原始分辨率大小的bitmap烈菌,內(nèi)存峰值可能很高。這一步的內(nèi)存消耗非常關(guān)鍵花履,如果圖片很大芽世,很容易就會(huì)增加幾十MB的內(nèi)存峰值。

這種方式的耗時(shí)不多诡壁,主要是內(nèi)存消耗巨大济瓢。

  • 推薦的方式

使用ImageIO的接口,避免調(diào)用UIImage的drawInRect:方法執(zhí)行帶來(lái)的中間bitmap的產(chǎn)生妹卿⊥可以在不產(chǎn)生Dirty Memory的情況下,直接讀取圖像大小和元數(shù)據(jù)信息夺克,不會(huì)帶來(lái)額外的內(nèi)存開(kāi)銷(xiāo)箕宙。其內(nèi)存消耗即為目標(biāo)尺寸需要的內(nèi)存。

   static func downsampling(imageWith imageData: Data, to pointSize: CGSize, scale: CGFloat) -> UIImage {
        let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
        let imageSource = CGImageSourceCreateWithData(imageData as CFData, imageSourceOptions)!

        let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
        let downsampleOptions = [
            kCGImageSourceCreateThumbnailFromImageAlways: true,
            kCGImageSourceCreateThumbnailWithTransform: true,
            kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels,
            kCGImageSourceShouldCacheImmediately: false
            ] as CFDictionary

        let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions)!
        /// Core Foundation objects returned from annotated APIs are automatically memory managed in Swift
        /// you do not need to invoke the CFRetain, CFRelease, or CFAutorelease functions yourself.
        return UIImage(cgImage: downsampledImage)
    }

其中铺纽,有一些選項(xiàng)設(shè)置downsampleOptions:

  1. kCGImageSourceCreateThumbnailFromImageAlways
  2. kCGImageSourceThumbnailMaxPixelSize
  3. kCGImageSourceShouldCache 可以設(shè)置為NO柬帕,避免緩存解碼后的數(shù)據(jù)。默認(rèn)為YES狡门。
  4. kCGImageSourceShouldCacheImmediately 可以設(shè)置為YES陷寝,避免在需要渲染的時(shí)候才做圖片解碼。默認(rèn)是NO其馏,不會(huì)立即進(jìn)行解碼渲染凤跑,而是在渲染時(shí)才在主線程進(jìn)行解碼。

而該downsampling過(guò)程非常占用CPU資源叛复,一定要放到異步線程去執(zhí)行饶火,否則會(huì)阻塞主線程鹏控。

緩存優(yōu)化

對(duì)于緩存數(shù)據(jù)或可重建數(shù)據(jù),盡量使用NSCache或NSPurableData肤寝,收到內(nèi)存警告時(shí)当辐,系統(tǒng)自動(dòng)處理內(nèi)存釋放操作,并且是線程安全的鲤看。

使用SDWebImage同時(shí)開(kāi)啟內(nèi)存和磁盤(pán)緩存時(shí)缘揪,若收到內(nèi)存警告,則內(nèi)存緩存的image被清除义桂。

加載超大圖片的正確姿勢(shì)

對(duì)于一些微信長(zhǎng)圖/微博長(zhǎng)圖之類(lèi)的找筝,或者一些需要展示全圖,然后拖動(dòng)來(lái)查看細(xì)節(jié)的場(chǎng)景慷吊,可以使用 CATiledLayer 來(lái)進(jìn)行分片加載袖裕,避免直接對(duì)圖片的所有部分進(jìn)行解碼和渲染,以節(jié)省資源溉瓶。在滑動(dòng)時(shí)急鳄,指定目標(biāo)位置,映射原圖指定位置的部分圖片進(jìn)行解碼和渲染堰酿。

進(jìn)入后臺(tái)

釋放占用較大的內(nèi)存疾宏,再次進(jìn)入前臺(tái)時(shí)按需加載。防止App在后臺(tái)時(shí)被系統(tǒng)殺掉触创。

一般監(jiān)聽(tīng)UIApplicationDidEnterBackground的系統(tǒng)通知即可坎藐。

ViewController相關(guān)的優(yōu)化

對(duì)于UITabBarController這樣有多個(gè)子VC的情況,切換tab時(shí)候哼绑,如果不顯示的ViewController依然占用較大內(nèi)存岩馍,可以考慮釋放,需要時(shí)候再加載抖韩。

UIView相關(guān)的優(yōu)化

  • 避免尺寸過(guò)大
    UIView尺寸過(guò)大時(shí)蛀恩,如果全部繪制,則會(huì)消耗大量?jī)?nèi)存帽蝶,以及阻塞主線程赦肋。常見(jiàn)的場(chǎng)景如微信消息的超長(zhǎng)文本块攒,則可將其分割成多個(gè)UIView励稳,然后放到UITableView中,利用cell的復(fù)用機(jī)制囱井,減少不必要的渲染和內(nèi)存占用驹尼。
  • 避免創(chuàng)建冗余的位圖 'Backing Store'
    減少drawRect的方法實(shí)現(xiàn),使用一些backgroundColor等屬性并不會(huì)創(chuàng)建backing store
  • 避免觸發(fā)離屏渲染
    使用cornerRadius并不會(huì)離屏渲染(開(kāi)辟臨時(shí)繪制buffer)庞呕,但使用mask會(huì)觸發(fā)離屏渲染新翎。

EXC_RESOURCE_EXCEPTION異常

iOS中沒(méi)有交換空間程帕,而是采用了JetSam機(jī)制。

當(dāng)App使用的內(nèi)存超出限制時(shí)地啰,系統(tǒng)會(huì)拋出EXC_RESOURCE_EXCEPTION異常愁拭。

內(nèi)存泄漏

內(nèi)存泄漏,有些是能通過(guò)工具檢測(cè)出來(lái)的亏吝。而還有一些無(wú)法檢測(cè)岭埠,需要自行分析。

  • 循環(huán)引用

通常對(duì)象間相互持有或者構(gòu)成環(huán)狀持有關(guān)系蔚鸥,則會(huì)引起循環(huán)引用惜论。

常見(jiàn)的有對(duì)象間引用、委托模式下的delegate止喷,以及Block引起的馆类。 其中block里捕獲了當(dāng)前對(duì)象obj的帶下劃線的私有變量,也會(huì)強(qiáng)引用了obj, 如果obj再持有block弹谁,那就會(huì)環(huán)引用了乾巧。

  • NSTimer
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(onTimerAction) userInfo:nil repeats:YES];

Timer不僅會(huì)持有target,也會(huì)持有userInfo對(duì)象, 如果target也直接或間接的持有了timer僵闯,則會(huì)造成環(huán)引用卧抗。

解決方法:

  1. 引入WeakContainer作為代理,弱持有target對(duì)象
  2. 通過(guò)擴(kuò)展NSTimer的Category鳖粟,引入帶block的初始化方法社裆,而block里弱持有了target

關(guān)于NSTimer可以參考更詳細(xì)的這篇博客:比較一下iOS中的三種定時(shí)器

  • 其他場(chǎng)景

一些濫用的單例,尤其是包含了不少block的單例向图,很容易產(chǎn)生內(nèi)存泄漏泳秀。排查時(shí)候需要格外細(xì)心。

離屏渲染

我們經(jīng)常會(huì)需要預(yù)先渲染文字/圖片以提高性能榄攀,此時(shí)需要盡可能保證這塊 context 的大小與屏幕上的實(shí)際尺寸一致嗜傅,避免浪費(fèi)內(nèi)存¢萦可以通過(guò) View Hierarchy 調(diào)試工具吕嘀,打印一個(gè) layer 的 contents 屬性來(lái)查看其中的 CGImage(backing image)以及其大小。layer的contents屬性即可看到其CGImage(backing store)的大小贞瞒。

Offscreen rendering is invoked whenever the combination of layer properties that have been specified mean that the layer cannot be drawn directly to the screen without pre- compositing. Offscreen rendering does not necessarily imply software drawing, but it means that the layer must first be rendered (either by the CPU or GPU) into an offscreen context before being displayed.

離屏渲染未必會(huì)導(dǎo)致性能降低偶房,而是會(huì)額外加重GPU的負(fù)擔(dān),可能導(dǎo)致一個(gè)V-sync信號(hào)周期內(nèi)军浆,GPU的任務(wù)未能完成棕洋,最終結(jié)果就是可能導(dǎo)致卡頓。

iOS系統(tǒng)對(duì)于Release環(huán)境下的優(yōu)化

實(shí)際的release環(huán)境下乒融,Apple會(huì)對(duì)一些場(chǎng)景自動(dòng)優(yōu)化掰盘,如release環(huán)境下摄悯,申請(qǐng)50MB的Dirty Memory,但實(shí)際footprint和resident不會(huì)增加50MB愧捕,具體Apple怎么做的不清楚奢驯。

減少缺頁(yè)次數(shù)

App啟動(dòng)時(shí),加載相應(yīng)的二進(jìn)制文件或者dylib到內(nèi)存中次绘。當(dāng)進(jìn)程訪問(wèn)一個(gè)虛擬內(nèi)存page叨橱,但該page未與物理內(nèi)存形成映射關(guān)系,則會(huì)觸發(fā)缺頁(yè)中斷断盛,然后再分配物理內(nèi)存罗洗。過(guò)多的缺頁(yè)中斷會(huì)導(dǎo)致一定的耗時(shí)。

二進(jìn)制重排的啟動(dòng)優(yōu)化方案钢猛,是通過(guò)減少App啟動(dòng)時(shí)候的缺頁(yè)中斷次數(shù)伙菜,來(lái)加速App啟動(dòng)。

字節(jié)對(duì)齊

當(dāng)定義object的時(shí)候命迈,盡量使得內(nèi)存頁(yè)對(duì)齊也會(huì)有幫助贩绕。小內(nèi)存屬性放一起,大內(nèi)存屬性放一起壶愤。

文字渲染

WWDC(WWDC18 219和416)上的結(jié)論淑倾,即黑白的位圖像素只占用 1 個(gè)字節(jié),比 4 字節(jié)節(jié)省 75% 的空間征椒。

image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末娇哆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子勃救,更是在濱河造成了極大的恐慌碍讨,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒙秒,死亡現(xiàn)場(chǎng)離奇詭異勃黍,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)晕讲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)覆获,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人瓢省,你說(shuō)我怎么就攤上這事弄息。” “怎么了净捅?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵疑枯,是天一觀的道長(zhǎng)辩块。 經(jīng)常有香客問(wèn)我蛔六,道長(zhǎng)荆永,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任国章,我火速辦了婚禮具钥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘液兽。我一直安慰自己骂删,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布四啰。 她就那樣靜靜地躺著宁玫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪柑晒。 梳的紋絲不亂的頭發(fā)上欧瘪,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音匙赞,去河邊找鬼佛掖。 笑死,一個(gè)胖子當(dāng)著我的面吹牛涌庭,可吹牛的內(nèi)容都是我干的芥被。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼坐榆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼拴魄!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起席镀,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤羹铅,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后愉昆,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體职员,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年跛溉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了焊切。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡芳室,死狀恐怖专肪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情堪侯,我是刑警寧澤嚎尤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站伍宦,受9級(jí)特大地震影響芽死,放射性物質(zhì)發(fā)生泄漏乏梁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一关贵、第九天 我趴在偏房一處隱蔽的房頂上張望遇骑。 院中可真熱鬧,春花似錦揖曾、人聲如沸落萎。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)练链。三九已至,卻和暖如春奴拦,著一層夾襖步出監(jiān)牢的瞬間兑宇,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工粱坤, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留隶糕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓站玄,卻偏偏與公主長(zhǎng)得像枚驻,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子株旷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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