<commit_before>---
layout:     post
title:      "Autorelease的实现原理"
date:       2017-11-26 22:52:00
author:     "dingjingpisces"
header-img: "img/post-bg-2015.jpg"
catalog: true
tags:
    - 技术
    - iOS开发
    - runtime
    - 源码 
---

# Autorelease的实现原理

Autorelease是苹果为开发者提供的用于垃圾回收的API，实现了在每个Runloop结束后自动释放无人引用的内存的功能。默认生成的代码会把我们要执行的代码包裹在AutoreleasePool里，在编译器的配合下，很多时候甚至感觉不到垃圾回收的存在。这篇文章希望通过源码分析Autorelease的实现原理。


## 使用场景

我理解，Autorelease的使用时机有两种：

1. 开发者手动将变量加入AutoreleasePool；
2. 函数返回时，如果有返回对象，编译器自动添加到AutoreleasePool;

## 关于返回值的优化

对于第2中情况，苹果做了优化，对象的引用计数不直接添加在AutoreleasePage里，而是经过TLS中转，直接传递给调用方，这么做还可以避免两侧多余的Retain,Release操作。具体逻辑如下.

被优化后的的被调用方(callee)会查看紧跟在return后的调用方(caller)指令。如果caller的指令也是经过优化的，那么callee会跳过所有引用计数操作（no autorelease,no retain release）。而是在TLS中设置标志位，经过优化的caller会查看TLS，如果发现设置了TLS值，则不对返回对象进行retain/release等操作，而是根据TLS中的值设置retainCount.

在runtime源码中，被优化的两个callee是：
objc_autoreleaseReturnValue  flag设置+1
objc_retainAutoreleaseReturnValue  flag设置+0

被优化的两个caller是：
objc_retainAutoreleasedReturnValue 调用者希望TLS值是+1
objc_unsafeClaimAutoreleasedRetainValue 调用者希望TLS值是+0

如:
Callee:
     return objc_autoreleaseReturnValue(ret)；// flag=1
Caller:
     ret = callee();
     ret = objc_retainAutoreleasedReturnValue(ret); //直接得到ret

Callee发现了优化的caller,设置TLS为+1
Caller查看TLS，清空，此时在没有retain, autorelease的情况下，callee,caller合作使ret得引用计数依然正确。

根据系统的不同，callee会识别一些特殊的机器码。


## AutoreleasePage 

Autorelease的实现数据结构是AutoreleasePage.AutoreleasePage中的地址，以数组形式组织，而AutoreleasePage本身以链表形式组织。
最新的Page称为hotPage,存储在TLS中，当新增一个obj到自动释放池时，如果hotPage已满或者不存在，会新生成一个Page，添加到原有链表中，并将该Page设置为hotPage

如下图所示：
![AutoreleasePage](https://github.com/dingjingpisces2015/dingjingpisces2015.github.io/blob/master/img/autoreleasePage.png?raw=true)

## 数据操作 (Push/Pop)

### Push
向自动释放池中添加一个对象，代码如下：


```
static inline void *push()
    {
        id *dest;
        if (DebugPoolAllocation) {<commit_msg>autorelease<commit_after>---
layout:     post
title:      "Autorelease的实现原理"
date:       2017-11-26 22:52:00
author:     "dingjingpisces"
header-img: "img/post-bg-2015.jpg"
catalog: true
tags:
    - 技术
    - iOS开发
    - runtime
    - 源码 
---

# Autorelease的实现原理

Autorelease是苹果为开发者提供的用于垃圾回收的API，实现了在每个Runloop结束后自动释放无人引用的内存的功能。默认生成的代码会把我们要执行的代码包裹在AutoreleasePool里，在编译器的配合下，很多时候甚至感觉不到垃圾回收的存在。这篇文章希望通过源码分析Autorelease的实现原理。


## 使用场景

我理解，Autorelease的使用时机有两种：

1. 开发者手动将变量加入AutoreleasePool；
2. 函数返回时，如果有返回对象，编译器自动添加到AutoreleasePool;

## 关于返回值的优化

对于第2中情况，苹果做了优化，对象的引用计数不直接添加在AutoreleasePage里，而是经过TLS中转，直接传递给调用方，这么做还可以避免两侧多余的Retain,Release操作。具体逻辑如下.

被优化后的的被调用方(callee)会查看紧跟在return后的调用方(caller)指令。如果caller的指令也是经过优化的，那么callee会跳过所有引用计数操作（no autorelease,no retain release）。而是在TLS中设置标志位，经过优化的caller会查看TLS，如果发现设置了TLS值，则不对返回对象进行retain/release等操作，而是根据TLS中的值设置retainCount.

在runtime源码中，被优化的两个callee是：
objc_autoreleaseReturnValue  flag设置+1
objc_retainAutoreleaseReturnValue  flag设置+0

被优化的两个caller是：
objc_retainAutoreleasedReturnValue 调用者希望TLS值是+1
objc_unsafeClaimAutoreleasedRetainValue 调用者希望TLS值是+0

如:
Callee:
     return objc_autoreleaseReturnValue(ret)；// flag=1
Caller:
     ret = callee();
     ret = objc_retainAutoreleasedReturnValue(ret); //直接得到ret

Callee发现了优化的caller,设置TLS为+1
Caller查看TLS，清空，此时在没有retain, autorelease的情况下，callee,caller合作使ret得引用计数依然正确。

根据系统的不同，callee会识别一些特殊的机器码。


## AutoreleasePage 

Autorelease的实现数据结构是AutoreleasePage.AutoreleasePage中的地址，以数组形式组织，而AutoreleasePage本身以链表形式组织。
最新的Page称为hotPage,存储在TLS中，当新增一个obj到自动释放池时，如果hotPage已满或者不存在，会新生成一个Page，添加到原有链表中，并将该Page设置为hotPage

如下图所示：
![AutoreleasePage](https://raw.githubusercontent.com/dingjingpisces2015/dingjingpisces2015.github.io/master/img/autoreleasePage.png)

## 数据操作 (Push/Pop)

### Push
向自动释放池中添加一个对象，代码如下：


```
static inline void *push()
    {
        id *dest;
        if (DebugPoolAllocation) {
{'fraction_non_alphanumeric': 0.08995983935742972, 'fraction_numerical': 0.018741633199464525, 'mean_word_length': 7.247240618101546, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '817435', 'n_tokens_mistral': 2181, 'n_tokens_neox': 2334, 'n_words': 150}