iOS原理 AutoreleasePool的基本概念

iOS原理 文章汇总

前言

一般情况下,对象在超出作用域时会立即release。比方说,在一个方法里创建一个局部对象:

-(void)test{
    
    NSObject *obj = [[NSObject alloc] init];
}

test方法执行完,这个NSObject对象就会被release了。但有些时候,比如从工厂方法返回对象时,并不希望对象在超出作用域后立即release,这就需要通过AutoreleasePool来实现。

AutoreleasePool的基本介绍

AutoreleasePool(自动释放池)是OC中的一种内存管理机制,它持有释放池里的对象的所有权,在自动释放池销毁时,统一给所有对象发送一次release消息。通过这个机制,可以延迟对象的释放。

  • 延迟释放
UIImage *img = [UIImage imageNamed:@"xxxx.png"];

这个UIImage对象是在类方法imageNamed里创建完后再返回,对象的所有权归方法持有,如果不延迟释放,在方法结束时对象就被释放了,返回的就为nil。因此,需要将对象先加入AutoreleasePool,所有权归自动释放池持有,只有自动释放池销毁时才释放,这样UIImage对象才能在方法结束后正常返回。

AutoreleasePool的创建方式

通常使用@autoreleasepool {}代码块来手动创建一个自动释放池

@autoreleasepool {
    //这里创建自动释放的对象,创建的对象会被加入到AutoreleasePool对象里
    ... ...
}

这个代码块等价于

{
    //创建一个AutoreleasePool对象
    __AtAutoreleasePool *atautoreleasepoolobj = objc_autoreleasePoolPush(); 
    
    //这里创建自动释放的对象,创建的对象会被加入到AutoreleasePool对象里
    ... ...    

   //给所有自动释放的对象发送一次release消息,并销毁AutoreleasePool对象
   objc_autoreleasePoolPop(atautoreleasepoolobj)
}

`{}`表示AutoreleasePool对象的作用域

代码块的实现逻辑如下:

  • 先通过调用objc_autoreleasePoolPush函数来创建一个AutoreleasePool对象。
  • 然后给在代码块里创建的每个自动释放的对象发送一个autorelease消息,将这些自动释放的对象加入到AutoreleasePool对象里。
  • 最后在AutoreleasePool对象将要销毁时,通过调用objc_autoreleasePoolPop函数给池中每个自动释放的对象发送一次release消息,再销毁AutoreleasePool对象。

注意区分AutoreleasePool对象自动释放的对象AutoreleasePool对象指的是实例化的一个自动释放池(本质也是对象),而 自动释放的对象是指被加入到这个池中的对象。
AutoreleasePool的原理可阅读后面的底层分析一文。

AutoreleasePool在Runloop中的创建和销毁

通常情况下,在平时开发中不需要手动创建自动释放池,因为Runloop会自动创建和销毁AutoreleasePool对象。

如上图所示,AutoreleasePoolRunloop中的创建和销毁的过程如下:

  • App启动后,系统在主线程RunLoop里注册了两个Observer,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()

  • 第一个Observer监视一个事件:

    • Entry(即将进入Loop):调用objc_autoreleasePoolPush来创建自动释放池。
  • 第二个Observer监视了两个事件:

    • Before waiting(准备进入休眠):先调用objc_autoreleasePoolPop销毁旧的自动释放池,再调用objc_autoreleasePoolPush创建一个新的自动释放池。
    • Exit(即将退出Loop):调用objc_autoreleasePoolPop销毁自动释放池。
  • 第一个observeorder-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
    第二个Observerorder2147483647,优先级最低,保证销毁自动释放池发生在其他所有回调之后。

也就是说,在一个RunLoop事件开始的时候会自动创建一个AutoreleasePool,在事件结束时再自动销毁。上面举例的imageNamed方法内部创建的对象也是加入到主线程RunLoop创建的AutoreleasePool中实现延迟释放的。因此,通常在开发中不需要开发者自己创建AutoreleasePool

手动创建AutoreleasePool的场景

虽然Runloop会自动创建和销毁自动释放池,但在有些情况下还是需要手动创建AutoreleasePool苹果官方文档建议在下面这三种情况下可能需要开发者创建自动释放池:

  • 编写不基于UI框架的程序,例如命令行工具。

    这一点的原因不是特别清楚,猜测是不基于UI框架的程序,可能不响应用户事件,导致不自动创建和销毁自动释放池。

  • 编写一个创建大量临时对象的循环。

    在循环内使用自动释放池块可以在下一次迭代之前释放这些对象,有助于减少应用程序的最大内存占用,即降低内存峰值

  • 编写非Cocoa程序时创建子线程。

    Cocoa程序中的每个线程都维护自己的自动释放池块堆栈。而编写一个非Cocoa程序,比如Foundation-only program,这时如果创建了子线程,若不手动创建自动释放池,自动释放的对象将会堆积得不到释放,导致内存泄漏。

这里就第二个场景举例,来说明在循环内使用AutoreleasePool对于降低内存峰值的作用。

//情况一:循环内不使用AutoreleasePool
for (int i = 0; i<1000000; i++) {

    NSString *string = [NSString stringWithFormat:@"%@", @"0123456789"];
    NSLog(@" ==== %p", string);
}

//情况二:循环内使用AutoreleasePool
for (int i = 0; i<1000000; i++) {

    @autoreleasepool {

        NSString *string = [NSString stringWithFormat:@"%@", @"0123456789"];
        NSLog(@" ==== %p", string);
    }
}

分别运行上面两种情况可以看到,在循环过程中,第一种情况的内存占用一直在增加,第二种情况的内存不会增加。这是因为:

  • 情况一:循环过程中,创建的NSString对象一直在堆积,只有在循环结束才一起释放,所以内存一直在增加。
  • 情况二:每一次迭代中都会创建并销毁一个AutoreleasePool,而每一次创建的NSString对象都会加入到AutoreleasePool中,所以在每次AutoreleasePool销毁时,NSString对象就会被释放,这样内存就不会增加。

这个场景中AutoreleasePool是通过立即释放对象来降低内存峰值,而前面又说自动释放池用来延迟对象的释放,这两者其实不矛盾,本质是一样的,都是在自动释放池销毁时调用objc_autoreleasePoolPop来释放池中的对象。只不过调用的时机不同,这里的@autoreleasepool {}是在超出自己的作用域时就调用函数来销毁,而前面的是在Runloop休眠或退出时才调用函数来销毁,所以调用的时机不同,才会实现立即或者延迟释放的目的。

@autoreleasepool {}的作用域指的就是前面提到的{},是AutoreleasePool对象的作用域。

哪些对象可以被添加到自动释放池?

MRC模式下,只要给对象发送autorelease消息,这个对象就会被添加到自动释放池。但在ARC模式下,是由编译器自动给对象发送autorelease消息,且不会给所有的对象都发送,只会给被编译器识别为自动释放的对象发送。一般来说,使用类方法(工厂方法)实例化的对象才是自动释放的对象,才能被添加到自动释放池,而使用new、alloc、copy关键字生成的对象和retain了的对象,不会被添加到自动释放池中。

  • UIImage对象为例
for (int i = 0; i<1000000; i++) {

    @autoreleasepool {

        //1.自动释放的对象,需要被添加到自动释放池中
        UIImage *image = [UIImage imageNamed:@"test.png"];
        
        //2.非自动释放的对象,不能被添加到自动释放池中
        UIImage *image = [[UIImage alloc] init];
        
        NSLog(@" ==== image = %p", image);
    }
}

分别运行上面两种情况,第一种情况内存不会增加,第二种情况内存会增加。第二种情况虽然在@autoreleasepool {}中创建对象,但由于不是自动释放的对象,所以还是不能被添加到AutoReleasePool中,只能在循环结束一起释放。因此,在ARC模式下,只有自动释放的对象才能被添加到AutoReleasePool中,非自动释放的对象在超出作用域时会被立即释放。

需要注意的是,自动释放的对象如果没有被添加到AutoReleasePool中,就会产生内存泄露。

总结

总得来说,关于AutoreleasePool的基本概念可以归纳以下几点:

  • AutoreleasePool(自动释放池)持有释放池里的对象的所有权,在自动释放池销毁时,统一给所有对象发送一次release消息。
  • 在一个RunLoop事件开始的时候会自动创建一个AutoreleasePool,在事件结束时再自动销毁,这样可以延迟对象的release
  • 也可以使用@autoreleasepool {}来手动创建自动释放池,在循环中使用可以立即释放对象,降低内存峰值。
  • MRC模式下,只要给对象发送autorelease消息,这个对象就会被添加到自动释放池。
  • ARC模式下,一般来说,使用类方法(工厂方法)实例化的对象才是自动释放的对象,才能被添加到自动释放池。自动释放的对象如果没有被添加到AutoReleasePool中,就会产生内存泄露。
  • ARC中,自动释放的对象由编译器自主识别并发送autorelease消息,添加到AutoReleasePool中。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容