iOS基礎(chǔ):深入內(nèi)存管理-讓人頭疼的autorelease

這篇文章其實(shí)是深入內(nèi)存管理:從所有權(quán)修飾符開始的補(bǔ)充。因?yàn)橛捎?code>__autoreleasing的試驗(yàn)過于多,都寫在上一篇文章中會(huì)使得文章篇幅結(jié)構(gòu)很難看,所以在這里新建一篇文章來記錄。

方法介紹

下面需要介紹兩個(gè)方法:
1._objc_rootRetainCount(id obj)方法,作用是返回obj的引用計(jì)數(shù)。
2._objc_autoreleasePoolPrint()方法,作用是打印當(dāng)前的自動(dòng)釋放池對象。
使用方法:
直接定義在類中就可以使用

extern uintptr_t _objc_rootRetainCount(id obj);
extern void _objc_autoreleasePoolPrint(void);

試驗(yàn)開始

一、基礎(chǔ)試驗(yàn)

下面有三個(gè)小實(shí)驗(yàn),為了證明結(jié)論:

在取得非自己生成并持有的對象時(shí),編譯器會(huì)默認(rèn)把對象注冊到自動(dòng)釋放池中。

也就是說:

編譯器為判斷方法名是否是以alloc/new/copy/mutableCopy開頭,如果不是,就自動(dòng)將返回的對象注冊到池子中。

1. 直接alloc方法初始化

代碼:

    id __weak obj0;
    {
        id obj1 = [[NSMutableArray alloc] init];
        obj0 = obj1;
        NSLog(@"%p", obj0);
        NSLog(@"%lu", _objc_rootRetainCount(obj0));
        _objc_autoreleasePoolPrint();
    }
    NSLog(@"obj0-1-%@", obj0);

實(shí)驗(yàn)結(jié)果:

程序到最后一行時(shí)輸出為空。

輸出:

試驗(yàn)1輸出.png

分析:

這里其實(shí)很簡單,編譯器的模擬代碼為

id obj1 = objc_msgSend(NSMutableArray, @selector(alloc));  
objc_msgSend(obj1, @selector(init));  
objc_release(obj1);  

當(dāng)變量obj1的作用域消失時(shí),編譯器會(huì)自動(dòng)插入objc_release(obj1);。這里并沒有涉及到自動(dòng)釋放池。所以array對象會(huì)被自動(dòng)銷毀。


2. 用array方法初始化

代碼

    id __weak obj0;
    {
        id obj2 = [NSMutableArray array];
        obj0 = obj2;
        NSLog(@"%p", obj2);
        NSLog(@"%lu", _objc_rootRetainCount(obj2));
        _objc_autoreleasePoolPrint();
    }
    NSLog(@"obj0-2-%@", obj0);

實(shí)驗(yàn)結(jié)果:

試驗(yàn)2輸出 1.png
試驗(yàn)2輸出 2.png

分析:

這里發(fā)現(xiàn)obj2的引用計(jì)數(shù)為2,再看自動(dòng)釋放池最后一個(gè)對象類型為__NSArrayM,正是obj2持有的對象。說明該對象已經(jīng)加入到了自動(dòng)釋放池中,所以最后輸出有值。那么這個(gè)對象什么時(shí)候釋放呢,我們后面說。


3. 用array方法初始化 并自己添加池子

代碼:

    @autoreleasepool {
        id obj3 = [NSMutableArray array];
        obj0 = obj3;
        NSLog(@"%p", obj3);
        NSLog(@"%lu", _objc_rootRetainCount(obj3));
        _objc_autoreleasePoolPrint();
    }
    NSLog(@"obj0-3-%@", obj0);

實(shí)驗(yàn)結(jié)果:

試驗(yàn)3輸出 1.png
試驗(yàn)3輸出 2.png

分析:

雖然這里和實(shí)驗(yàn)2一樣被放入到了池子中,但是最后打印還是為空。說明在退出池子后,對象被銷毀了。


4.總結(jié)

以上三個(gè)實(shí)驗(yàn)驗(yàn)證了之前提出的結(jié)論。
下面以第三個(gè)實(shí)驗(yàn)作為例子分析代碼:

    @autoreleasepool {
        // [NSMutableArray array]返回的對象會(huì)被默認(rèn)加入到池子中
        // 對象的引用計(jì)數(shù)為1
        // obj3默認(rèn)為__strong,強(qiáng)引用array對象
        // array對象的引用計(jì)數(shù)為2
        id obj3 = [NSMutableArray array];
        // obj0對__weak 因此對象引用計(jì)數(shù)不變
        obj0 = obj3;
        NSLog(@"%p", obj3);
        NSLog(@"%lu", _objc_rootRetainCount(obj3));
        _objc_autoreleasePoolPrint();
    }
    // obj3的作用域結(jié)束,釋放對象 計(jì)數(shù)-1
    // 池子結(jié)束 池子中的對象要被釋放 計(jì)數(shù)-1
    // 對象計(jì)數(shù)為0 因此銷毀
    // 輸出為空
    NSLog(@"obj0-3-%@", obj0);

二、再次驗(yàn)證試驗(yàn)一

下面驗(yàn)證自己定義的方法是否也可以遵守以上結(jié)論。

1.不以alloc等開頭的方法

代碼:

    {
        id obj5 = [[self class] Object];
        obj0 = obj5;
        NSLog(@"%p", obj0);
        NSLog(@"%lu", _objc_rootRetainCount(obj0));
        _objc_autoreleasePoolPrint();
    }
    NSLog(@"obj0-5-%@", obj0);
+ (id)Object
{
    id array = [[NSMutableArray alloc] init];
    return array;
}

輸出:

試驗(yàn)4輸出 1.png
試驗(yàn)4輸出 2.png

分析:

最后輸出沒有問題,確實(shí)是加入到了池子中。但是為什么這里的計(jì)數(shù)是3呢???

2.以alloc等開頭的方法

代碼:

    {
        id obj6 = [[self class] allocObject];
        obj0 = obj6;
        NSLog(@"%p", obj0);
        NSLog(@"%lu", _objc_rootRetainCount(obj0));
        _objc_autoreleasePoolPrint();
    }
    NSLog(@"obj0-6-%@", obj0);
+ (id)allocObject
{
    id array = [[NSMutableArray alloc] init];
    return array;
}

輸出:

試驗(yàn)5輸出 1.png
試驗(yàn)5輸出 2.png

分析:

這里的引用計(jì)數(shù)變成2了,但是沒有加入到池子中,且最后輸出為空。說明這里多出的1的引用計(jì)數(shù)來自+ (id)allocObject方法。

3.總結(jié)

自定義的方法仍然可以說明實(shí)驗(yàn)一的結(jié)論。

三、何時(shí)釋放

下面定義一個(gè)屬性@property (nonatomic, weak) id obj;,并在- (void)viewDidLoad中賦值。

- (void)viewDidLoad
{
    [super viewDidLoad];
    id obj = [NSMutableArray array];
    self.obj = obj;
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    NSLog(@"%p", self.obj);
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    NSLog(@"%p", self.obj);
}

輸出:

2017-04-28 11:23:54.829 MRCTest[57782:5898069] 0x6000000523f0
2017-04-28 11:23:54.832 MRCTest[57782:5898069] 0x0

為什么在viewWillAppear中還存在而到了viewDidAppear中就為空了?我猜測是否是執(zhí)行完viewWillAppear后,自動(dòng)釋放池銷毀了,導(dǎo)致array對象也銷毀了。
其實(shí)原因是viewDidLoadviewWillAppear是在同一個(gè)RunLoop中調(diào)用的,而viewDidAppear與他們不是同一個(gè),因此當(dāng)RunLoop一圈結(jié)束時(shí),池子被銷毀,里面的對象也自然被銷毀了。

最后

其實(shí)我想寫一點(diǎn)__strong以及__autoreleasing底層代碼的,但是因?yàn)樽约阂矝]有完全理解,所以還是不亂寫了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

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

  • 29.理解引用計(jì)數(shù) Objective-C語言使用引用計(jì)數(shù)來管理內(nèi)存,也就是說,每個(gè)對象都有個(gè)可以遞增或遞減的計(jì)數(shù)...
    Code_Ninja閱讀 1,747評論 1 3
  • 內(nèi)存管理 ARC處理原理 ARC是Objective-C編譯器的特性,而不是運(yùn)行時(shí)特性或者垃圾回收機(jī)制,ARC所做...
    b485c88ab697閱讀 11,348評論 3 47
  • 前言 現(xiàn)在iOS開發(fā)已經(jīng)是arc甚至是swift的時(shí)代,但是內(nèi)存管理仍是一個(gè)重點(diǎn)關(guān)注的問題,如果只知盲目開發(fā)而不知...
    明仔Su閱讀 26,805評論 16 175
  • 內(nèi)存管理是程序在運(yùn)行時(shí)分配內(nèi)存、使用內(nèi)存,并在程序完成時(shí)釋放內(nèi)存的過程。在Objective-C中,也被看作是在眾...
    蹲瓜閱讀 3,365評論 1 8
  • *面試心聲:其實(shí)這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個(gè)offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,628評論 30 472

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