6. 自动释放池 autorelease

@autoreleasepool 原理

==@autoreleasepool==

实现原理:以栈为节点通过双向链表形式组合而成的

编译期

@autoreleasepool {} 被转换为一个 __AtAutoreleasePool 结构体:

{
    __AtAutoreleasePool __autoreleasepool;
}

结构体会在初始化时调用 objc_autoreleasePoolPush() 方法,会在析构时调用 objc_autoreleasePoolPop

//每个AutoreleasePoolPage 的大小都是 4096 字节(16 进制 0x1000)
class AutoreleasePoolPage {
    magic_t const magic;
    id *next;//next 指向了下一个为空的内存地址
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
};
//magic 用于对当前 AutoreleasePoolPage 完整性的校验
//thread 保存了当前页所在的线程
//自动释放池中的 AutoreleasePoolPage 是以双向链表的形式连接起来的
//parent 和 child 就是用来构造双向链表的指针。
==AutoreleasePoolPage::push==
  • 有 AutoreleasePoolPage 并且当前 page 不满

调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中

  • 有 AutoreleasePoolPage 并且当前 page 已满

调用 autoreleaseFullPage 初始化一个新的页
调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中

  • 无 AutoreleasePoolPage

调用 autoreleaseNoPage 创建一个 hotPage
调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
最后的都会调用 page->add(obj) 将对象添加到自动释放池中。

==AutoreleasePoolPage::pop==
  1. 使用 pageForPointer 获取当前 token 所在的 AutoreleasePoolPage
  2. 调用 releaseUntil 方法释放栈中的对象,直到 stop
  3. 调用 child 的 kill 方法
==autorelease 方法==

在 autorelease 方法的调用栈中,最终都会调用 autoreleaseFast 方法,将当前对象加到 AutoreleasePoolPage 中。

==释放时机==

在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop。

- (void)viewDidLoad {
    [super viewDidLoad];
    @autoreleasepool {
        NSString *str = [NSString stringWithFormat:@"sunnyxx"];
    }
    NSLog(@"%@", str); // Console: (null)
    //手动干预下在括号结束时释放。
}

Autorelease返回值的快速释放机制

ARC runtime有一套对autorelease返回值的优化策略。

+ (instancetype)createSark {
    id tmp = [self new];
    return objc_autoreleaseReturnValue(tmp); // 代替我们调用autorelease
}
  • Thread Local Storage

Thread Local Storage(TLS优化)线程局部存储,目的很简单,将一块内存作为某个线程专有的存储,以key-value的形式进行读写。

在MRC时代,autoreleased对象的创建与持有是以以下形式进行的:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    ViewController *obj = [ViewController viewController];
    [obj retain];
    [obj release];
}
//函数依次调用中autorelease、retain的作用效果又是相互抵消的
//所以在ARC时代编译器会根据函数的调用列表来判断autorelease之后是否紧接着调用了retain,如果是则将原本需要push进Autoreleasepool的对象直接返回给函数的调用方

原本的代码被转换成:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    id temp = objc_retainAutoreleasedReturnValue([ViewController viewController]);
    ViewController *obj = temp
}

使用pthread提供的方法实现:

void* pthread_getspecific(pthread_key_t);
int pthread_setspecific(pthread_key_t , const void *);
//在返回值身上调用objc_autoreleaseReturnValue方法时,
//runtime将这个返回值object储存在TLS中,然后直接返回这个object(不调用autorelease);
//同时,在外部接收这个返回值的objc_retainAutoreleasedReturnValue里
//发现TLS中正好存了这个对象,那么直接返回这个object(不调用retain)。
//于是乎,调用方和被调方利用TLS做中转,很有默契的免去了对返回值的内存管理。
  • 其他Autorelease相关知识点

使用容器的block版本的枚举器时,内部会自动添加一个AutoreleasePool:

[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 这里被一个局部@autoreleasepool包围着
}];
当然,在普通for循环和for in循环中没有

==为什么autoreleasepool可以嵌套:==

是因为每次创建autoreleasepool时,实际上是插入哨兵对象的过程。next指针置为哨兵对象(nil),转移向下一位地址,添加对象。

==Autoreleasepool对象从哪里来?==

对于每一个Runloop运行循环,系统会隐式创建一个Autoreleasepool对象

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

  • 第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。

  • 第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

什么对象自动加入到 autoreleasepool中

  • 第一种

当使用alloc/new/copy/mutableCopy开始的方法进行初始化时,会生成并持有对象(也就是不需要pool管理,系统会自动的帮他在合适位置release)

   例如: NSObject *stu = [[NSObject alloc] init]; 

那么对于其他情况,例如

id obj = [NSMutableArray array];
//这种情况会自动将返回值的对象注册到autorealeasepool,代码等效于:
@autorealsepool{
    id __autorealeasing obj = [NSMutableArray array];
}
  • 第二种

__weak修饰符只持有对象的弱引用,而在访问引用对象的过程中,该对象可能被废弃。那么如果把对象注册到autorealeasepool中,那么在@autorealeasepool块结束之前都能确保对象的存在。

最新情况下编译器运行说明

参考:https://stackoverflow.com/questions/40993809/why-weak-object-will-be-added-to-autorelease-pool

The new implmenetation of __weak of Apple LLVM version 8.0.0 (clang-800.0.42.1) do not postpond the release to autoreleasepool, but use objc_release directly.

  • 第三种

id或对象的指针在没有显式指定时会被附加上__autorealeasing修饰符 如: ==形参==

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