iOS內(nèi)存管理

概要

Objective-c中的內(nèi)存管理,也就是引用計(jì)數(shù)。提供了兩種內(nèi)存管理機(jī)制MRC(Mannul Reference Counting)和ARC(Automatic Reference Counting)。

  1. MRC(手動(dòng)引用計(jì)數(shù)),手動(dòng)管理內(nèi)存。
    MRC模式下,所有的對(duì)象都需要手動(dòng)的添加retain、release代碼 來(lái)管理內(nèi)存。使用MRC,需 要遵守誰(shuí)創(chuàng)建,誰(shuí)回收的原則。也就是誰(shuí)alloc,誰(shuí)release;誰(shuí)retain,誰(shuí)release。當(dāng)引用計(jì)數(shù)為0的時(shí)候,必須回收,引用計(jì)數(shù)不為0,不能回收,如果引用計(jì)數(shù)為0,但是沒(méi)有回 收,會(huì)造成內(nèi)存泄露。如果引用計(jì)數(shù)為0,繼續(xù)釋放,會(huì)造成野指針。為了避免出現(xiàn)野指針,我們?cè)卺尫诺臅r(shí)候,會(huì)先讓指針置nil。
  2. ARC(自動(dòng)引用計(jì)數(shù)),自動(dòng)管理內(nèi)存。
    ARC是IOS5推出的新功能,通過(guò)ARC,可以自動(dòng)的管理內(nèi)存。在ARC模式下,只要沒(méi)有強(qiáng)指針(強(qiáng)引用)指向?qū)ο?,?duì)象就會(huì)被釋放。在ARC模式下,不允許使用retain、release、 retainCount等方法。并且,如果使用dealloc方法時(shí),不允許調(diào)用[super dealloc]方法。

1.引用計(jì)數(shù)

看到“引用計(jì)數(shù)”大家很容易想到“某處有某物多少個(gè)”,而Objc采用引用計(jì)數(shù)的思想是:

  1. 當(dāng)對(duì)象被創(chuàng)建(通過(guò)alloc、new、copy、MutableCopy等方法)時(shí),其引用計(jì)數(shù)初始值為1。
  2. 被其他變量引用而持有時(shí),其引用計(jì)數(shù)加1。
  3. 其他變量使用結(jié)束而釋放時(shí),其引用計(jì)數(shù)減1。
  4. 當(dāng)引用計(jì)數(shù)的值變?yōu)?時(shí),表示對(duì)象沒(méi)有被任何代碼使用,此時(shí)對(duì)象將被釋放。
    如果1-1所示:


    引用計(jì)數(shù)的內(nèi)存管理

示例:

@implementation Test

- (instancetype)init {
    if (self = [super init]) {
        NSLog(@"對(duì)象被創(chuàng)建,初始引用計(jì)數(shù)為%ld", self.retainCount);
    }
    return self;
}

- (void)dealloc {
    NSLog(@"對(duì)象被銷(xiāo)毀");
    [super dealloc];
}

@end

在main.m中創(chuàng)建Test對(duì)象

    NSObject *obj = [[Test alloc]init];
    
    NSObject *obj1 = [obj retain];
    NSLog(@"對(duì)象進(jìn)行retain操作,引用計(jì)數(shù):%ld",obj.retainCount);
    
    [obj1 release];
    NSLog(@"retain對(duì)象進(jìn)行release操作,引用計(jì)數(shù):%ld",obj.retainCount);
    
    [obj release];

輸出結(jié)果為:

2017-11-07 20:24:48.047429+0800 MemoryManger[15557:2996092] 對(duì)象被創(chuàng)建,初始引用計(jì)數(shù)為1
2017-11-07 20:24:48.047605+0800 MemoryManger[15557:2996092] 對(duì)象進(jìn)行retain操作,引用計(jì)數(shù):2
2017-11-07 20:24:48.047780+0800 MemoryManger[15557:2996092] retain對(duì)象進(jìn)行release操作,引用計(jì)數(shù):1
2017-11-07 20:24:48.047989+0800 MemoryManger[15557:2996092] 對(duì)象被銷(xiāo)毀

注:調(diào)用或釋放已經(jīng)銷(xiāo)毀的對(duì)象時(shí)會(huì)導(dǎo)致程序奔潰


15101041233195.jpg

2. autoreleasePool

自動(dòng)釋放池顧名思義,就是一個(gè)池子,可以容納對(duì)象,并且自動(dòng)釋放。為什么需要自動(dòng)釋放池了?因?yàn)楫?dāng)一個(gè)對(duì)象不再使用時(shí)應(yīng)該將其釋放,但是什么時(shí)間釋放?這個(gè)時(shí)間點(diǎn)很難確定。Objc提供autorelease,當(dāng)一個(gè)對(duì)象接收到autorelease消息時(shí),它會(huì)被注冊(cè)到當(dāng)前的自動(dòng)釋放池,當(dāng)自動(dòng)釋放池被銷(xiāo)毀時(shí),會(huì)給池子里的所有對(duì)象發(fā)送release消息將其釋放,但在這個(gè)時(shí)間段內(nèi),對(duì)象可以正常使用。

它與release的區(qū)別如圖1-2所示:


autorelease與release的區(qū)別

在Cocoa框架中,相當(dāng)于程序主循環(huán)的NSRunLoop或者在其他程序可運(yùn)行的地方,對(duì)NSAutoreleasePool對(duì)象進(jìn)行生成,持有和廢棄處理。如果1-3所示:


15101072086365.jpg

注:

  1. autorelease不會(huì)改變對(duì)象的引用計(jì)數(shù)
  2. 自動(dòng)釋放池實(shí)質(zhì)上只是在銷(xiāo)毀的時(shí)候給池中的所有對(duì)象發(fā)送release消息,并不能保證對(duì)象一定被釋放,如果接受release消息的對(duì)象的引用計(jì)數(shù)仍大于1,對(duì)象就無(wú)法釋放
  3. 自動(dòng)釋放池中的對(duì)象會(huì)集中同一時(shí)間釋放,如果操作需要生成的對(duì)象較多占用內(nèi)存空間大,會(huì)產(chǎn)生內(nèi)存不足的現(xiàn)象。典型的例子就是讀入大量圖像的同時(shí)改變其尺寸。圖像文件讀入到NSData對(duì)象,并從中生成UIImageview對(duì)象,改變對(duì)象尺寸后生成新的UIImageview對(duì)象。這種情況下就會(huì)產(chǎn)生大量autorelease對(duì)象,可以創(chuàng)建內(nèi)部的池子來(lái)降低內(nèi)存占用峰值。

3. MRC內(nèi)存管理基本原則

簡(jiǎn)單的說(shuō)就是:誰(shuí)創(chuàng)建,誰(shuí)釋放;誰(shuí)retain,誰(shuí)release

  1. 當(dāng)通過(guò)alloc,new,copy,MutableCopy方法創(chuàng)建一個(gè)對(duì)象時(shí),它的引用計(jì)數(shù)為1,當(dāng)不再使用該對(duì)象時(shí),應(yīng)該向?qū)ο蟀l(fā)送release消息或autorelease消息釋放對(duì)象。
  2. 如果獲得對(duì)象所有權(quán),就需要保留對(duì)象并在操作完成之后釋放,保證retain和release成對(duì)出現(xiàn)。

4. ARC

4.1 所有權(quán)修飾符

ARC有4中修飾符: __strong, __weak, __autoreleasing, __unsafe_unretained(為兼容iOS5以下版本的產(chǎn)物,可以理解成MRC下的weak)

  1. __strong:表示對(duì)對(duì)象的“強(qiáng)引用”,無(wú)修飾符情況下的默認(rèn)值。
  2. __weak:弱引用,不持有所指向?qū)ο蟮乃袡?quán).
    注:
    1). 互相強(qiáng)引用,很容易引起循環(huán)引用問(wèn)題。__weak修飾符的變量不持有對(duì)象,所以在超出其變量作用域時(shí),對(duì)象即被釋放。故在可能發(fā)生循環(huán)引用的類(lèi)成員變量改用__weak修飾,即可避免。如圖所示:



    __weak修飾符避免循環(huán)引用

2). 弱引用指向的對(duì)象被釋放后,引用本身會(huì)置nil,避免野指針。

    id __weak obj0 = nil;
    {
        id  obj1 = [[NSObject alloc]init]; // obj1變量為強(qiáng)引用
        obj0 = obj1;                       // objc0變量持有對(duì)象的弱引用
        NSLog(@"A:%@",obj0);               // 輸出objc0變量持有的弱引用的對(duì)象
    }
    /*
     * 1. objc1變量超出其作用域,強(qiáng)引用失效,自動(dòng)釋放自己持有的對(duì)象
     * 2. 對(duì)象無(wú)持有者,對(duì)象被銷(xiāo)毀
     * 3. 持有改對(duì)象的弱引用obj0變量的弱引用失效,賦值為nil
     */
    NSLog(@"B:%@",obj0);

輸出結(jié)果:

2017-11-08 13:52:01.008357+0800 MemoryManger[19017:3772013] A:<NSObject: 0x600000010070>
2017-11-08 13:52:01.008508+0800 MemoryManger[19017:3772013] B:(null)

3). __autoreleasing:自動(dòng)釋放對(duì)象的引用,一般用于傳遞參數(shù)。
比如解析數(shù)據(jù)方法:

+ (nullable id)JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError **)error;

調(diào)用時(shí)會(huì)發(fā)現(xiàn)如圖提示

編譯器會(huì)將源代碼轉(zhuǎn)化成以下形式:

 NSError *error = nil;
 NSError * __autoreleasing tmp = error;
 id value = [NSJSONSerialization JSONObjectWithData:self options:kNilOptions error:&tmp];

5. 屬性的內(nèi)存管理

屬性的修飾詞可以分為三類(lèi):

  1. 原子性:nonatomic,atomic
  • nonatomic:非原子性訪問(wèn),不加同步,多線程并發(fā)訪問(wèn)會(huì)提高性能。
  • atomic:為原子操作,是防止在寫(xiě)未完成的時(shí)候被另外一個(gè)線程讀取,造成數(shù)據(jù) 錯(cuò)誤。這種機(jī)制是耗費(fèi)系統(tǒng)資源。
  1. 讀寫(xiě)屬性:readwrite,readonly
  • readwrite:是默認(rèn)屬性,生成getter和setter方法
  • readonly:只生成getter方法
  1. 內(nèi)存屬性:assign、strong/retain 、weak、copy
  • assign:直接賦值,索引計(jì)數(shù)不改變,適用于基本數(shù)據(jù)類(lèi)型
  • strong/retain:release舊值,retain新值(用于OC對(duì)象)
    使用set方法賦值時(shí),實(shí)質(zhì)上是會(huì)先保留新值,再釋放舊值,再設(shè)置新值,避免新舊值一樣時(shí)導(dǎo)致對(duì)象被釋放的的問(wèn)題。
MRC寫(xiě)法:
- (void)setTitle:(NSString *)title {
        [title retain];
        [_title release];
    _title = title;
}
ARC寫(xiě)法:
- (void)setTitle:(NSString *)title {
    _title = title;
}

  • copy : release舊值,copy新值。一般用來(lái)修飾String、Dict、Array等需要保護(hù)其封裝性的對(duì)象,尤其是在其內(nèi)容可變的情況下,因此會(huì)拷貝(深拷貝)一份內(nèi)容給屬性使用,避免可能造成的對(duì)源內(nèi)容進(jìn)行改動(dòng)。

  • weak:對(duì)對(duì)象的一種弱引用,不保留新值,也不釋放舊值,只設(shè)置新值。


希望本文能拋磚引玉,幫助大家對(duì)內(nèi)存管理有更多的理解。如有不當(dāng)之處,希望大家能多多指點(diǎn)我。


References:
《Objective-C高級(jí)編程》

最后編輯于
?著作權(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),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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