先弄清楚這里的學(xué)問,再來談 iOS 內(nèi)存管理與優(yōu)化(二)

上篇文章講述了iOS內(nèi)存管理的基本概念,這里是一些內(nèi)存優(yōu)化的小技巧

Strong Weak Dance

經(jīng)常使用閉包的話,應(yīng)該會(huì)特別注意這一條吧。

先將強(qiáng)引用的對(duì)象轉(zhuǎn)為弱引用指針,防止了Block和對(duì)象之間的循環(huán)引用。再在Block的中,將weakSelf的弱引用轉(zhuǎn)換成strongSelf這樣的強(qiáng)引用指針,防止了多線程和ARC環(huán)境下弱引用隨時(shí)被釋放的問題

__weak MyViewController *wself = self;
self.completionHandler = ^(NSInteger result) {
    __strong __typeof(wself) sself = wself; // 強(qiáng)引用一次
    [sself.property removeObserver: sself forKeyPath:@"pathName"];
};

降低內(nèi)存峰值

Lazy Allocation

延時(shí)加載是很常用的一種優(yōu)化方法,如果有些情況我們不會(huì)立即使用某一對(duì)象和某些資源,我們完全可以在使用的時(shí)候再進(jìn)行加載,這些就可以避免初次運(yùn)行程序的時(shí)候內(nèi)存消耗嚴(yán)重。

lazy var goodsImageView: UIImageView = {
        let goodsImageView = UIImageView()
        return goodsImageView
    }()

喵神的博客中,也發(fā)現(xiàn)了很好玩的地方,Swift標(biāo)準(zhǔn)庫中,定義了一些Lazy方法,可以配合像 map或是 filter 這類接受閉包并進(jìn)行運(yùn)行的方法一起,讓整個(gè)行為變成延時(shí)進(jìn)行。

let data = 1...3
let result = data.lazy.map {
    (i: Int) -> Int in
    print("正在處理 \(i)")
    return i * 2
}

print("準(zhǔn)備訪問結(jié)果")
for i in result {
    print("操作后結(jié)果為 \(i)")
}

print("操作完畢")

運(yùn)行結(jié)果為:

// 準(zhǔn)備訪問結(jié)果
// 正在處理 1
// 操作后結(jié)果為 2
// 正在處理 2
// 操作后結(jié)果為 4
// 正在處理 3
// 操作后結(jié)果為 6
// 操作完畢

圖片的讀取

相信寫代碼的時(shí)候都會(huì)涉及到圖片的讀取,我們經(jīng)常使用的方法

imageView?.image = UIImage(named: name)
imageView?.image = UIImage(contentsOfFile: path)
  • 對(duì)于第一種,是帶緩存機(jī)制的,如果頻繁讀取小文件,用它就只需要讀取一次就好,但是缺點(diǎn)就是如果使用大圖片會(huì)常駐內(nèi)存,對(duì)于降低內(nèi)存峰值是不利的。
  • 對(duì)于第二種方法,不帶緩存機(jī)制,適合使用大圖片,使用完就釋放

NSData & 內(nèi)存映射文件

在我們經(jīng)常使用NSData時(shí),出鏡率很高的兩個(gè)方法

public init?(contentsOfFile path: String)
public init(contentsOfFile path: String, options readOptionsMask: NSDataReadingOptions) throws

第二種比第一個(gè)多一個(gè)Options,第二種方式是創(chuàng)建了一個(gè)內(nèi)存映射文件,把內(nèi)容放在虛擬內(nèi)存中,只有讀取操作的時(shí)候才會(huì)讀到相對(duì)應(yīng)頁的物理內(nèi)存頁中。所以后者,對(duì)于大文件是很劃算的,推薦使用第二種。對(duì)于可選的方式如下:

映射

其他方式

莊延軍老師的分享中,還有其他一些技巧,但是對(duì)于只懂Swift的我,實(shí)在沒接觸過哪些方案,等我接觸之后,再來寫第三個(gè)分享。下面簡單列舉一下:

  • calloc VS malloc + memset
  • 棧內(nèi)存分配

NSAutoReleasePool

MRC時(shí)代說起,當(dāng)需要我們手動(dòng)來管理對(duì)象引用計(jì)數(shù)的時(shí)候,我們需要RetainRelease方法,那么可能一個(gè)線程中有大量的RetainRelease方法,這個(gè)時(shí)候使用一個(gè)池一起管理他們是不是方便很多,將線程中要執(zhí)行的任務(wù)都放在自動(dòng)釋放池中,自動(dòng)釋放池會(huì)捕獲所有任務(wù)中的對(duì)象,在任務(wù)結(jié)束或線程關(guān)閉之時(shí)自動(dòng)釋放這些對(duì)象。

當(dāng)然自動(dòng)釋放池都會(huì)使用NSAutoreleasePool類創(chuàng)建,并在自動(dòng)釋放池收到 drain消息時(shí)將這些對(duì)象的引用計(jì)數(shù)減一,然后將它們從池子中移除 (這一過程形象地稱為“抽干池子”)。

ARC時(shí)代,不再使用NSAutoreleasePool來創(chuàng)建自動(dòng)釋放池,而是使用@autoreleasepool代碼塊,新建Objective-C工程的時(shí)候,會(huì)幫我們創(chuàng)建一個(gè)main.m文件,已經(jīng)幫我們?cè)谥骶€程創(chuàng)建了一個(gè)自動(dòng)釋放池。

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

更進(jìn)一步,其實(shí)@autoreleasepool 在編譯時(shí)會(huì)被展開為 NSAutoreleasePool,并且在每個(gè)主Runloop結(jié)束時(shí)進(jìn)行drain操作。

那么到了Swift時(shí)代,又具有了新的變化,不需要手動(dòng)地調(diào)用autorelease 這樣的方法來管理引用計(jì)數(shù),但是這些方法還是都會(huì)被調(diào)用的,只不過是編譯器在編譯時(shí)在合適的地方幫我們加入了而已。

在Swift標(biāo)準(zhǔn)庫中可以發(fā)現(xiàn):

public func autoreleasepool(@noescape code: () -> ())

那么我們就可以利用閉包的特點(diǎn),在需要的時(shí)候非常簡便的使用它

autoreleasepool { 
    // 線程執(zhí)行任務(wù)的邏輯代碼
}

AutoReleasePool

為什么在ARC時(shí)代還需要使用自動(dòng)釋放池呢?其原因就是為了避免內(nèi)存峰值,那比如說我有一個(gè)很大的For循環(huán),里面不斷生成autorelease
對(duì)象(比如:dataWithContentsOfFile返回的是 autorelease 的對(duì)象)。其實(shí)每迭代一次,資源都已經(jīng)用完了(就是說我用好了,還你),不需要再用了,這個(gè)時(shí)候就可以釋放了,但是程序需要等到Runloop結(jié)束的時(shí)候才可以釋放,這就增大了內(nèi)存的峰值。

上面的話有點(diǎn)繞,但是真心非常重要。

那么怎么做,可以減小內(nèi)存峰值呢?我們可以在For循環(huán)中添加autoreleasepool,這樣就可以保證每次迭代完畢一次就可以釋放點(diǎn)內(nèi)存。

func loadBigData() {
    for i in 1...10000 {
        autoreleasepool {
            let data = NSData.dataWithContentsOfFile(
                path, options: nil, error: nil)
        }
    }
}

簡單總結(jié)

上面的闡述了多種方法,其中對(duì)于降低內(nèi)存峰值有作用的是:

  • Lazy Allocation
  • 圖片的讀取的正確方式
  • NSData & 內(nèi)存映射文件
  • callocVS malloc + memset
  • 棧內(nèi)存分配
  • autoReleasePool

內(nèi)存警告處理

到收到內(nèi)存警告的時(shí)候,所要做的:

  • 盡可能多的釋放資源,尤其是圖片等占用內(nèi)存多的資源,等需要的時(shí)候再進(jìn)行重建
  • 單例模式的濫用,會(huì)導(dǎo)致單例對(duì)象一直持有資源,在內(nèi)存緊張的時(shí)候要進(jìn)行釋放,當(dāng)然我也在其他博客中看到一些單例模式的替換方案
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 29.理解引用計(jì)數(shù) Objective-C語言使用引用計(jì)數(shù)來管理內(nèi)存,也就是說,每個(gè)對(duì)象都有個(gè)可以遞增或遞減的計(jì)數(shù)...
    Code_Ninja閱讀 1,753評(píng)論 1 3
  • 前言 現(xiàn)在iOS開發(fā)已經(jīng)是arc甚至是swift的時(shí)代,但是內(nèi)存管理仍是一個(gè)重點(diǎn)關(guān)注的問題,如果只知盲目開發(fā)而不知...
    明仔Su閱讀 26,819評(píng)論 16 175
  • 內(nèi)存管理 簡述OC中內(nèi)存管理機(jī)制。與retain配對(duì)使用的方法是dealloc還是release,為什么?需要與a...
    丶逐漸閱讀 2,086評(píng)論 1 16
  • 現(xiàn)在iOS開發(fā)已經(jīng)是ARC甚至是swift的時(shí)代,但是內(nèi)存管理仍是一個(gè)重點(diǎn)關(guān)注的問題,如果只知盲目開發(fā)而不知個(gè)中原...
    碼代碼的小馬閱讀 631評(píng)論 0 1
  • 1.1 什么是自動(dòng)引用計(jì)數(shù) 概念:在 LLVM 編譯器中設(shè)置 ARC(Automaitc Reference Co...
    __silhouette閱讀 5,484評(píng)論 1 17

友情鏈接更多精彩內(nèi)容