揭开ARC的神秘面纱系列-第3话

博客地址

“揭开ARC的神秘面纱系列”的这篇续集全都是关于@autoreleasepool这一新指令的。LLVM提及到autorelease pools(自动释放池)的语义已经在LLVM3.0版本中发生变化,尤其是,我觉得探究ARC模式更新之后是如何实现的会很有意思。

因此,思考一下下面的函数:


    void foo() {
        @autoreleasepool {
            NSNumber *number = [NSNumber numberWithInt:0];
            NSLog(@"number = %p", number);
        }
    }

显然,这完全是不和谐的代码段,但是它能让我看到发生什么。在非ARC模式下,我们可能会假设:number将会在numberWithInt:函数中被分配内存,并返回的是一个自动释放的对象。因此当自动释放池随后被销毁时,number对象将会被释放。所以让我们看看是否如上所述(一如往常,使用的是ARMv7指令集):


    .globl  _foo
        .align  2
        .code   16
        .thumb_func     _foo
    _foo:
        push    {r4, r7, lr}
        add     r7, sp, #4
        blx     _objc_autoreleasePoolPush
        movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_0+4))
        movs    r2, #0
        movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_0+4))
        mov     r4, r0
        movw    r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC0_1+4))
    LPC0_0:
        add     r1, pc
        movt    r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC0_1+4))
    LPC0_1:
        add     r0, pc
        ldr     r1, [r1]
        ldr     r0, [r0]
        blx     _objc_msgSend
        mov     r1, r0
        movw    r0, :lower16:(L__unnamed_cfstring_-(LPC0_2+4))
        movt    r0, :upper16:(L__unnamed_cfstring_-(LPC0_2+4))
    LPC0_2:
        add     r0, pc
        blx     _NSLog
        mov     r0, r4
        blx     _objc_autoreleasePoolPop
        pop     {r4, r7, pc}

不错,答案是肯定的。正是这样的。我们可以看到函数先将自动释放池入栈,然后调用numberWithInt:函数,然后将自动释放池出栈。正如我们所预料的。现在我们看看完全相同的代码在ARC模式编译出来是怎么样的:


    .globl  _foo
        .align  2
        .code   16
        .thumb_func     _foo
    _foo:
        push    {r4, r5, r7, lr}
        add     r7, sp, #8
        blx     _objc_autoreleasePoolPush
        movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_0+4))
        movs    r2, #0
        movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_0+4))
        mov     r4, r0
        movw    r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC0_1+4))
    LPC0_0:
        add     r1, pc
        movt    r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC0_1+4))
    LPC0_1:
        add     r0, pc
        ldr     r1, [r1]
        ldr     r0, [r0]
        blx     _objc_msgSend
        @ InlineAsm Start
        mov     r7, r7          @ marker for objc_retainAutoreleaseReturnValue
        @ InlineAsm End
        blx     _objc_retainAutoreleasedReturnValue
        mov     r5, r0
        movw    r0, :lower16:(L__unnamed_cfstring_-(LPC0_2+4))
        movt    r0, :upper16:(L__unnamed_cfstring_-(LPC0_2+4))
        mov     r1, r5
    LPC0_2:
        add     r0, pc
        blx     _NSLog
        mov     r0, r5
        blx     _objc_release
        mov     r0, r4
        blx     _objc_autoreleasePoolPop
        pop     {r4, r5, r7, pc}

留意上述代码中objc_retainAutoreleasedReturnValue函数和objc_release的调用。ARC已经为我们做了决定,完全不必担心自动释放池,因为ARC可以直接不然自动释放池生效,通过调用objc_retainAutoreleasedReturnValue函数对number对象进行retain一次,然后在后面在调用objc_release函数释放它。这意味着自动释放池的逻辑不一定执行,让人满意的结果。

注意到自动释放池一直需要入栈和出栈,是因为ARC无法知晓numberWithInt函数和NSLog函数中会发生什么,不知道在函数中是否有对象会被加入释放池。如果说ARC知道这两个函数不会自动释放任何东西则实际上可以移除自动释放池的入栈和出栈操作。也许这种逻辑在ARC未来的版本中出现,尽管我不是很确定那时候ARC的语义会如何实现。

现在让我思考另外一个例子,在这个例子中我们想要在自动释放池的作用域之外使用number对象。这应该告诉我们为什么ARC是一个神奇的工具。思考下面的代码:


    void bar() {
        NSNumber *number;
        @autoreleasepool {
            number = [NSNumber numberWithInt:0];
            NSLog(@"number = %p", number);
        }
        NSLog(@"number = %p", number);
    }

你可能会认为上述这段看似很和谐的代码会出问题。问题在于number对象将在自动释放池中创建,在自动释放池初衷时被释放,但是却在释放之后继续使用。噢!让我们通过在非ARC模式下编译上述代码来看看我们的猜想是否是正确的:


    .globl  _bar
        .align  2
        .code   16
        .thumb_func     _bar
    _bar:
        push    {r4, r5, r6, r7, lr}
        add     r7, sp, #12
        blx     _objc_autoreleasePoolPush
        movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4))
        movs    r2, #0
        movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4))
        mov     r4, r0
        movw    r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC1_1+4))
    LPC1_0:
        add     r1, pc
        movt    r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC1_1+4))
    LPC1_1:
        add     r0, pc
        ldr     r1, [r1]
        ldr     r0, [r0]
        blx     _objc_msgSend
        movw    r6, :lower16:(L__unnamed_cfstring_-(LPC1_2+4))
        movt    r6, :upper16:(L__unnamed_cfstring_-(LPC1_2+4))
    LPC1_2:
        add     r6, pc
        mov     r5, r0
        mov     r1, r5
        mov     r0, r6
        blx     _NSLog
        mov     r0, r4
        blx     _objc_autoreleasePoolPop
        mov     r0, r6
        mov     r1, r5
        blx     _NSLog
        pop     {r4, r5, r6, r7, pc}

很明显,正如我们所期望的那样没有调用retain,release或者autorelease,因为我们没有显式调用这些函数以及使用ARC。编译的结果也正如我们之前推理的那样。接下来让我们在ARC的帮助下会是什么样:


    .globl  _bar
        .align  2
        .code   16
        .thumb_func     _bar
    _bar:
        push    {r4, r5, r6, r7, lr}
        add     r7, sp, #12
        blx     _objc_autoreleasePoolPush
        movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4))
        movs    r2, #0
        movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC1_0+4))
        mov     r4, r0
        movw    r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC1_1+4))
    LPC1_0:
        add     r1, pc
        movt    r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC1_1+4))
    LPC1_1:
        add     r0, pc
        ldr     r1, [r1]
        ldr     r0, [r0]
        blx     _objc_msgSend
        @ InlineAsm Start
        mov     r7, r7          @ marker for objc_retainAutoreleaseReturnValue
        @ InlineAsm End
        blx     _objc_retainAutoreleasedReturnValue
        movw    r6, :lower16:(L__unnamed_cfstring_-(LPC1_2+4))
        movt    r6, :upper16:(L__unnamed_cfstring_-(LPC1_2+4))
    LPC1_2:
        add     r6, pc
        mov     r5, r0
        mov     r1, r5
        mov     r0, r6
        blx     _NSLog
        mov     r0, r4
        blx     _objc_autoreleasePoolPop
        mov     r0, r6
        mov     r1, r5
        blx     _NSLog
        mov     r0, r5
        blx     _objc_release
        pop     {r4, r5, r6, r7, pc}

此处应该有掌声!ARC识别出我们在自动释放池作用域之外使用了number对象,因此它如上一段代码一样对numberWithInt:函数的返回值进行了retain,但是这一次它将release操作放在了bar函数末尾而不是自动释放池出栈的时候。这一举措避免在一些代码中出现崩溃,我们可能会认为这些代码是正确的,但实际上却潜在着内存管理的bug。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容

  • 29.理解引用计数 Objective-C语言使用引用计数来管理内存,也就是说,每个对象都有个可以递增或递减的计数...
    Code_Ninja阅读 1,485评论 1 3
  • 博客地址 以下是正文: 写完第一篇关于揭开ARC神秘面纱的博客,我想和大家分享另外一些有趣的片段。这一次我好奇当你...
    IcebergHorseman阅读 302评论 0 0
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,139评论 30 470
  • 博客地址 这个系列一共有四篇博客,是Matt Galloway大神关于ARC的内部实现的一些探索,看完之后觉得收获...
    IcebergHorseman阅读 386评论 0 0
  • 内存管理 简述OC中内存管理机制。与retain配对使用的方法是dealloc还是release,为什么?需要与a...
    丶逐渐阅读 1,961评论 1 16