iOS中autorelease的那些事儿

前言

在MRC下, 我们需要手动管理内存, 写一大堆的retain, release代码, 稍不留神就会造成内存泄露; 而ARC下, 编译器帮我们屏蔽掉了这些繁琐的代码, 我们不需要再一条一条地写retain, release了, 可以专心地把精力放在业务逻辑, 技术上.
在MRC下, 调用[object autorelease]可以延迟对象的内存释放; 在ARC下, 我们甚至可以不需要知道 autorelease 是什么都能管理好内存. 编译器帮我们做了什么事情? 到底 autorelease 有什么神奇的地方? autorelease pool 又是个什么东西?下面我将会一一道来.

NSAutoreleasePool 与 @autoreleasepool

NSAutoreleasePool 是 Cocoa 用来支持引用计数内存管理机制的类, 当一个autorelease pool(自动释放池)被drain(销毁)的时候会对pool里的对象发送一条release的消息.
注意 : 在ARC下, 不能使用NSAutoreleasePool这个类来创建自动释放池, 而应该用@autoreleasepool { } 这个block, 官方文档源码如下 :

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Code benefitting from a local autorelease pool.
[pool release];

用以下代码代替上述代码 :

@autoreleasepool {
    // Code benefitting from a local autorelease pool.
}
Ps : 官方文档说明, 使用@autoreleasepool这个block比NSAutoreleasePool更高效!并且在MRC环境下同样适用*

让我们用一张图片来了解对象调用autorelease的整个过程


Object autorelease

补充几点 :

  1. 苹果官方文档说An object can be put into the same pool several times, in which case it receives a release message for each time it was put into the pool. 意思是一个对象可以多次放入同一池子中, 并且每次放进去的时候都会调用release方法.. 但是我在MRC下做了测试, 结果是每调用一次autorelease方法, 池子中就多一个对象. 调用5次之后打印池子, 发现池子中有5个相同的对象.... 有知道的朋友麻烦告诉下谢谢
  2. 程序中至少存在一个自动释放池, 否则autoreleased对象将不能对应收到release消息而导致内存泄露.


    自动释放池
  3. NSAutoreleasePool对象不能retain, 不能autorelease, 所以drain方法(或者release方法, 但是这两者有所不同, 下文会说)可以直接释放内存. 你应该在同一个上下文(调用创建这个池的同一个方法, 函数或者循环体)中drain一个自动释放池.
  4. MRC下需要对象调用autorelease才会入池, ARC下可以通过__autoreleasing修饰符, 否则的话看方法名, 非alloc/new/copy/mutableCopy开头的方法编译器都会自动帮我们调用autorelease方法.
  5. 不一定要自己创建自动释放池, 但是有3种情况下是很必要的, 下面会讲.
  6. 自动释放池可以嵌套使用

下面讲autorelease pool 与 线程, RunLoop的关系...

autorelease pool 与 线程

每一个线程(包括主线程)都有一个NSAutoreleasePool栈. 当一个新的池子被创建的时候, push进栈. 当池子被释放内存时, pop出栈. 对象调用autorelease方法进入栈顶的池子中. 当线程结束的时候, 它会自动地销毁掉所有跟它有关联的池子.

线程中的自动释放池栈

如果你的应用或者线程是长期存在的并且有可能产生大量的autoreleased对象, 你应该定期地drain和create自动释放池, 否则, autorelease对象会在内存中堆积造成内存告急. 这里借用土土哥的一张图,
循环体中是否使用autoreleasepool的区别

测试的内容:500000次循环,每次循环创建一个NSNumber实例和两个NSString实例。
图:红线表示没有用@autoreleasepool时的内存占用。
图:绿线表示用了@autoreleasepool优化后的内存占用!

如上图所示, 在每一次循环中, 先创建一个自动释放池, 然后循环结束的时候, 自动释放池销毁, release掉池中对象, 释放内存. 对比之下, 优劣不言而喻.
苹果也是这么做的, 数组的block遍历方法就是, 大家可以自行测试.

数组的block遍历方法

autorelease pool 与 RunLoop

autorelease pool 与 RunLoop

程序运行 -> 开启事件循环 -> 发生触摸事件 -> 创建自动释放池 -> 处理触摸事件 -> 事件对象加入自动释放池 -> 一次事件循环结束, 销毁自动释放池.

苹果官方文档说 :
The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event

在开始每一个事件循环之前系统会在主线程创建一个自动释放池, 并且在事件循环结束的时候把前面创建的释放池释放, 回收内存. 这里不深入讲解RunLoop, 文章后面会给出几篇RunLoop的文章, 大家可以去看看.

如何管理自动释放池

这里介绍4个方法

  1. release
  2. drain
  3. autorelease
  4. retain

release 和 drain

这里把他们放在一块讲, 是因为他们在引用计数环境下都能销毁一个自动释放池, 为什么这里要特意说明引用计数环境, 因为在引用计数环境和垃圾回收(GC)环境下, 这两个方法不尽相同
引用计数环境下, release 和 drain 效果相同, 均能销毁一个自动释放池.
垃圾回收环境下, drain同上, release 则是一个空方法.
所以建议为了兼容性, 统一用drain吧.

autorelease 和 retain

抛出异常, 因为NSAutoreleasePool不能调用以上 autorelease 和 retain 方法.

怎么使用autorelease pool

由于@autoreleasepool同时兼容MRC和ARC编译环境(NSAutoreleasePool只能在MRC下使用), 所以以下均是以autorelease pool block来介绍使用.
Cocoa 希望代码总是在autorelease pool block中被执行, 否则autoreleased对象就得不到释放从而造成内存泄露.

什么时候需要自己手动创建autorelease pool

看苹果官方文档怎么说明 :

  1. If you are writing a program that is not based on a UI framework, such as a command-line tool.

  2. If you write a loop that creates many temporary objects.
    You may use an autorelease pool block inside the loop to dispose of those objects before the next iteration. Using an autorelease pool block in the loop helps to reduce the maximum memory footprint of the application.

  3. If you spawn a secondary thread.
    You must create your own autorelease pool block as soon as the thread begins executing; otherwise, your application will leak objects.

  4. 你写的程序不是基于UI framework, 例如命令行项目

  5. 你写的循环创建了大量临时对象 -> 你需要在循环体内创建一个autorelease pool block并且在每次循环结束之前处理那些autoreleased对象. 在循环中使用autorelease pool block可以降低内存峰值

  6. 你创建了一个新线程
    当线程开始执行的时候你必须立马创建一个autorelease pool block, 否则你的应用会造成内存泄露.

使用场景 :

  1. 利用@autoreleasepool优化循环, 如上述提过的例子所示
  2. 如果你的应用程序或者线程是要长期运行的并且有可能产生大量autoreleased对象, 你应该使用autorelease pool blocks
  3. 长期在后台中运行的任务, 方法

使用方法 : 不要太简单~~

    @autoreleasepool {
        // Code here
    }
这里介绍一种特殊的情况

先上苹果官方源码

– (id)findMatchingObject:(id)anObject {
    id match;
    while (match == nil) {
      @autoreleasepool {
      // Do a search that creates a lot of temporary objects. 
      match = [self expensiveSearchForObject:anObject];
      if (match != nil) {
        [match retain]; /* Keep match around. */
        }
      }
    }
    return [match autorelease]; /* Let match go and return it. */
}

在block结束之后, 你要注意的是任何autoreleased对象已经被处理过了(release). 请不要对这个对象发送消息或者把这个对象当做方法的返回值返回. 会引发野指针错误.
解决方法 : 苹果是这么做的 : 在block内对match对象发送retain消息和在block外对match发送autorelease消息能延长match对象的生命周期并且允许match对象在block外部接收消息或者作为方法的返回值返回. 我们不需要再关心match什么时候释放, 因为它已经交给了上一层的autorelease pool去管理.

参考文档 :
NSAutoreleasePool Class Reference
Using Autorelease Pool Blocks
黑幕背后的Autorelease
@autoreleasepool-内存的分配与释放

这里推荐两个RunLoop的文章
深入理解RunLoop
RunLoop


欢迎大家关注@Jerry4me, 同时本文有错漏的点恳请大家不吝指出, 谢谢~

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

推荐阅读更多精彩内容