目录
-
- ARC与MRC
- 项目中的main函数
- 自动释放池
- [@autoreleasepool {}实现原理](#@autoreleasepool {}实现原理)
ARC与MRC
苹果在 iOS 5 中引入了ARC(Automatic Reference Counting)
自动引用计数内存管理技术,通过LLVM
编译器和Runtime
协作来进行自动管理内存
LLVM
编译器会在编译时在合适的地方为 OC 对象插入retain
、release
和autorelease
代码,省去了在MRC(Manual Reference Counting)
手动引用计数下手动插入这些代码的工作,减轻了开发者的工作量
在MRC下,当我们不需要一个对象的时候,要调用release
或autorelease
方法来释放它:
- 调用
release
会立即让对象的引用计数减 1 ,如果此时对象的引用计数为 0,对象就会被销毁
- 调用
autorelease
会将该对象添加进自动释放池中,它会在一个恰当的时刻自动给对象调用release
,所以autorelease
相当于延迟了对象的释放
项目中的main函数
main
函数在整个iOS项目中是一个非常不起眼的函数,却是整个iOS程序的入口
objectivec
// main.m
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool { // 自动释放池
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
上面main函数的返回值说明,所有的事件、消息全部交给了UIApplication
来处理,意味着整个iOS的应用都是包含在一个自动释放池里的
自动释放池
@autoreleasepool {}实现原理
objectivec
// main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person* person = [[[Person alloc] init]];
}
return 0;
}
我们使用clang
命令将main.m
文件转换为main.cpp
文件,cpp文件内容如下:
objectivec
int main(int argc, const char * argv[]) {
/* @autoreleasepool */
{ __AtAutoreleasePool __autoreleasepool;
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")));
}
return 0;
}
发现会生成一个__AtAutoreleasePool
结构体
objectivec
struct __AtAutoreleasePool {
__AtAutoreleasePool() { // 构造函数,在生成结构体变量的时候调用
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool() { // 析构函数,在结构体销毁的时候调用
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
在@autoreleasepool
这个块作用域内:
- 开头声明了一个
__AtAutoreleasePool
结构体类型局部变量,这时会调用构造函数objc_autoreleasePoolPush
- 结尾等作用域结束,局部变量被销毁,调用析构函数
objc_autoreleasePoolPop
看一下push
和pop
的实现:
objectivec
void *objc_autoreleasePoolPush(void) {
// 调用了AutoreleasePoolPage中的push方法
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt) {
// 调用了AutoreleasePoolPage中的pop方法
AutoreleasePoolPage::pop(ctxt);
}
AutoreleasePoolPage
分析一下这个核心的类AutoreleasePoolPage
,其本质是AutoreleasePoolPageData
类:
objectivec
#define PAGE_MIN_SHIFT 12
#define PAGE_MIN_SIZE (1 << PAGE_MIN_SHIFT)
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
friend struct thread_data_t;
public:
// 表示一个空池子
# define EMPTY_POOL_PLACEHOLDER ((AutoreleasePoolPage*)1)
// 哨兵对象
# define POOL_BOUNDARY nil
// 每页的大小
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
PAGE_MAX_SIZE; // must be multiple of vm page size
#else
PAGE_MIN_SIZE; // size and alignment, power of 2
#endif
private:
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
static size_t const COUNT = SIZE / sizeof(id);
static size_t const MAX_FAULTS = 2;
// ....
}
// AutoreleasePoolPageData
struct AutoreleasePoolPageData
{
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
struct AutoreleasePoolEntry {
uintptr_t ptr: 48;
uintptr_t count: 16;
static const uintptr_t maxCount = 65535; // 2^16 - 1
};
static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
#endif
magic_t const magic;
__unsafe_unretained id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent; // 指向上一个AutoreleasePoolPage的指针(链表中的第一个为nil)
AutoreleasePoolPage *child; // 指向下一个存储AutoreleasePoolPage的指针(链表中的最后一个为nil)
// 代表深度,第一个page的depth为0,往后每递增一个page,depth会加1
uint32_t const depth;
// 表示high water mark(最高水位标记)
uint32_t hiwat;
AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
: magic(), next(_next), thread(_thread),
parent(_parent), child(nil),
depth(_depth), hiwat(_hiwat)
{
}
};
PAGE_MIN_SIZE
:从AutoreleasePoolPage类中可以看出,每个page对象的大小为1 << 12
,即2的12次方,4096字节magic
:对当前AutoreleasePoolPage 完整性的校验,就是用来判断对象是否完成初始化的一个标志next
:指向下一个即将产生的autoreleased对象的存放位置(当next == begin()
时,表示AutoreleasePoolPage为空;当next == end()
时,表示AutoreleasePoolPage已满thread
:当前线程,表明page与线程有关child
和parent
:表明每个page对象是通过双向链表联系起来的depth
:代表深度代表深度,第一个page的depth为0,往后每递增一个page,depth会加1hiwat
:表示high water mark(最高水位标记)
总结
@autoreleasepool
底层会生成一个__AtAutoreleasePool
变量,此变量内部又会生成objc_autoreleasePoolPush
和objc_autoreleasePoolPop
两个函数分别在作用域的开始和结尾进行push(入栈)和pop(出栈)操作,这两个操作都是依靠于AutoreleasePoolPage
类的,AutoreleasePoolPage
是一个双向链表结构
- 当进入@autoreleasepool作用域时,objc_autoreleasePoolPush 方法被调用, runtime 会向当前的 AutoreleasePoolPage 中添加一个 nil 对象作为哨兵对象,并返回该哨兵对象的地址;
- 对象调用autorelease方法,会被加入到对应的的AutoreleasePoolPage中去,next指针类似一个游标,不断变化,记录位置。如果加入的对象超出一页的大小,便会自动加一个新页。
- 当离开@autoreleasepool作用域时,objc_autoreleasePoolPop(哨兵对象地址)方法被调用,其会从当前 page 的 next 指标的上一个元素开始查找, 直到最近一个哨兵对象, 依次向这个范围中的对象发送release消息
因为哨兵对象的存在,自动释放池的嵌套也是满足的,不管是嵌套还是被嵌套的自动释放池,找自己对应的哨兵对象就行了
objc_autoreleasePoolPush的源码分析
objc_autoreleasePoolPush -> AutoreleasePoolPage::push
:
objectivec
// 入栈
static inline void *push()
{
id *dest;
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.
// 创建一个新的page对象,将POOL_BOUNDARY加进去
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
// 已有page对象,快速加入POOL_BOUNDARY
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
autoreleaseNewPage
如果是一个空池,那么会调用autoreleaseNewPage
:
objectivec
static __attribute__((noinline))
id *autoreleaseNewPage(id obj)
{
// 获取当前操作页
AutoreleasePoolPage *page = hotPage();
// 将POOL_BOUNDARY加到page中(入栈)
if (page) return autoreleaseFullPage(obj, page);
else return autoreleaseNoPage(obj);
}
// 获取当前操作页
static inline AutoreleasePoolPage *hotPage()
{
// 获取当前页
AutoreleasePoolPage *result = (AutoreleasePoolPage *)
tls_get_direct(key);
// 如果是一个空池,则返回nil,否则,返回当前线程的自动释放池
if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
if (result) result->fastcheck();
return result;
}
autoreleaseNewPage内部判断有无page,有就调用autoreleaseFullPage
将对象压入栈,否则调用autoreleaseNoPage
创建新的page,然后再进行压栈操作
autoreleaseFullPage
池子中有page,直接入栈
objectivec
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
// The hot page is full.
// Step to the next non-full page, adding a new page if necessary.
// Then add the object to that page.
ASSERT(page == hotPage());
ASSERT(page->full() || DebugPoolAllocation);
// 循环遍历当前page是否满了
do {
// 如果子页面存在,则将页面替换为子页面
if (page->child) page = page->child;
// 如果子页面不存在,则新建页面
else page = new AutoreleasePoolPage(page);
} while (page->full());
// 设置为当前操作page
setHotPage(page);
// 压入栈
return page->add(obj);
}
// 设置当前操作页
static inline void setHotPage(AutoreleasePoolPage *page)
{
if (page) page->fastcheck();
tls_set_direct(key, (void *)page);
}
static inline AutoreleasePoolPage *coldPage()
{
AutoreleasePoolPage *result = hotPage();
if (result) {
while (result->parent) {
result = result->parent;
result->fastcheck();
}
}
return result;
}
add
压栈
objectivec
id *add(id obj) {
assert(!full());
unprotect();
// 传入对象存储的位置
id *ret = next; // faster than `return next-1` because of aliasing
// 将obj压栈到next指针位置,然后进行next++,即下一个对象存储的位置
*next++ = obj;
protect();
return ret;
}
autoreleaseNoPage
池子中无page,创建新的page,再入栈
objectivec
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
// "No page" could mean no pool has been pushed
// or an empty placeholder pool has been pushed and has no contents yet
ASSERT(!hotPage());
bool pushExtraBoundary = false;
// 判断是否为空占位符,如果是,则将入栈标识为true
if (haveEmptyPoolPlaceholder()) {
// We are pushing a second pool over the empty placeholder pool
// or pushing the first object into the empty placeholder pool.
// Before doing that, push a pool boundary on behalf of the pool
// that is currently represented by the empty placeholder.
pushExtraBoundary = true;
}
// 如果不是POOL_BOUNDARY,并且没有pool,则报错
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
// We are pushing an object with no pool in place,
// and no-pool debugging was requested by environment.
_objc_inform("MISSING POOLS: (%p) Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
objc_thread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
// 如果对象是POOL_BOUNDARY,且没有申请自动释放池内存,则设置一个空占位符存储在tls中,其目的是为了节省内存
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
// We are pushing a pool with no pool in place,
// and alloc-per-pool debugging was not requested.
// Install and return the empty pool placeholder.
return setEmptyPoolPlaceholder();
}
// We are pushing an object or a non-placeholder'd pool.
// Install the first page.
// 初始化第一页
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
// 设置为当前页
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
// 如果标识为true,则压入栈
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
return page->add(obj);
}
autoreleaseFast
如果一开始就有page页面,不是空池子,那么直接进入到autoreleaseFast
,再分别进行判断
objectivec
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) { // 已有page,并且没满
return page->add(obj);
} else if (page) {
// 如果满了,则安排新的page
return autoreleaseFullPage(obj, page);
} else {
// page不存在,新建
return autoreleaseNoPage(obj);
}
}
总结
- 每一个
AutoreleasePoolPage
对象都会有一定的存储空间,大概占用4096个字节 - 每一个
AutoreleasePoolPage
对象内部的成员变量会占56个字节,然后剩余的空间才用来存储autorelease
对象 - 每一个
@autoreleasePool
的开始都会先将POOL_BOUNDARY
对象压入栈,然后才开始存储autorelease
对象,并且push方法会返回POOL_BOUNDARY
对象的内存地址 - 当一个
AutoreleasePoolPage
对象存满后才会往下一个AutoreleasePoolPage
对象里开始存储 AutoreleasePoolPage
对象里面的begin
和end
分别对应着autorelease对象开始入栈的起始地址和结束地址AutoreleasePoolPage
对象里面的next
指向下一个能存放autorelease对象地址的区域
autorelease方法源码分析
autorelease方法底层会调用objc_object::rootAutorelease()
函数
objectivec
// objc_object::autorelease
inline id
objc_object::autorelease()
{
ASSERT(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
return rootAutorelease();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));
}
// objc_object::rootAutorelease
inline id
objc_object::rootAutorelease()
{
// 如果是TaggedPointer就返回
if (isTaggedPointer()) return (id)this;
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
return rootAutorelease2();
}
// objc_object::rootAutorelease2
__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{
ASSERT(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
最后还是会调用到AutoreleasePoolPage
的autorelease
objectivec
static inline id autorelease(id obj)
{
ASSERT(!obj->isTaggedPointerOrNil());
id *dest __unused = autoreleaseFast(obj);
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || (id)((AutoreleasePoolEntry *)dest)->ptr == obj);
#else
ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
#endif
return obj;
}
然后进入到快速压栈,autoreleaseFast进行压栈操作,autoreleasepool只会将调用了autorelease的对象压入栈
autorelease和objc_autoreleasePush的整体分析如下图所示:
objc_autoreleasePoolPop的源码分析
objc_autoreleasePoolPop -> AutoreleasePoolPage::pop
:
objectivec
static inline void
pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
// 判断是否为空占位符
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
// 获取当前页
page = hotPage();
if (!page) {
// Pool was never used. Clear the placeholder.
// 如果当前页不存在,则清除空占位符
return setHotPage(nil);
}
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
// 如果当前页存在,则将当前页设置为coldPage,token设置为coldPage的开始位置
page = coldPage();
token = page->begin();
} else {
// 获取token所在的page
page = pageForPointer(token);
}
stop = (id *)token;
// 判断最后一个位置,是否是POOL_BOUNDARY
if (*stop != POOL_BOUNDARY) {
// 如果不是,即最后一个位置是一个对象
if (stop == page->begin() && !page->parent) {
// Start of coldest page may correctly not be POOL_BOUNDARY:
// 1. top-level pool is popped, leaving the cold page in place
// 2. an object is autoreleased with no pool
// 如果是第一个位置,且没有父节点,什么也不做
} else {
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
// 如果是第一个位置,且有父节点,则出现了混乱
return badPop(token);
}
}
if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
return popPageDebug(token, page, stop);
}
// 出栈
return popPage<false>(token, page, stop);
}
begin
和end
分别对应着autorelease对象的起始地址和结束地址
objectivec
// 开始存放autorelease对象的地址:开始地址 + 他本身占用的大小
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
// 结束地址:开始地址 + PAGE_MAX_SIZE
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
// coldPage
static inline AutoreleasePoolPage *coldPage()
{
AutoreleasePoolPage *result = hotPage();
if (result) {
while (result->parent) {
result = result->parent;
result->fastcheck();
}
}
return result;
}
popPage
objectivec
template<bool allowDebug>
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();
// 出栈当前操作页面对象
page->releaseUntil(stop);
// memory: delete empty children
// 删除空子项
if (allowDebug && DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
// 获取当前页面的父节点
AutoreleasePoolPage *parent = page->parent;
//删除将当前页面
page->kill();
// 设置操作页面为父节点页面
setHotPage(parent);
} else if (allowDebug && DebugMissingPools && page->empty() && !page->parent) {
// special case: delete everything for pop(top)
// when debugging missing autorelease pools
page->kill();
setHotPage(nil);
} else if (page->child) {
// hysteresis: keep one empty child if page is more than half full
// 如果页面已满一半以上,则保留一个空子级
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}
// kill
void kill()
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
AutoreleasePoolPage *page = this;
while (page->child) page = page->child;
AutoreleasePoolPage *deathptr;
do {
deathptr = page;
// 子节点 变成 父节点
page = page->parent;
if (page) {
page->unprotect();
//子节点置空
page->child = nil;
page->protect();
}
delete deathptr;
} while (deathptr != this);
}
内部会调用releaseUntil
循环遍历进行pop操作
releaseUntil
objectivec
void releaseUntil(id *stop)
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
// 循环遍历
// 判断下一个对象是否等于stop,如果不等于,则进入while循环
while (this->next != stop) {
// Restart from hotPage() every time, in case -release
// autoreleased more objects
AutoreleasePoolPage *page = hotPage();
// fixme I think this `while` can be `if`, but I can't prove it
// 如果当前页是空的
while (page->empty()) {
// 将page赋值为父节点页
page = page->parent;
// 并设置当前页为父节点页
setHotPage(page);
}
page->unprotect();
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
AutoreleasePoolEntry* entry = (AutoreleasePoolEntry*) --page->next;
// create an obj with the zeroed out top byte and release that
id obj = (id)entry->ptr;
int count = (int)entry->count; // grab these before memset
#else
id obj = *--page->next;
#endif
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
if (obj != POOL_BOUNDARY) { // 只要不是POOL_BOUNDARY,就进行release
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
// release count+1 times since it is count of the additional
// autoreleases beyond the first one
for (int i = 0; i < count + 1; i++) {
objc_release(obj);
}
#else
objc_release(obj);
#endif
}
}
// 设置当前页
setHotPage(this);
#if DEBUG
// we expect any children to be completely empty
for (AutoreleasePoolPage *page = child; page; page = page->child) {
ASSERT(page->empty());
}
#endif
}
总结
pop
函数会将POOL_BOUNDARY
的内存地址传进去- autorelease对象从end的结束地址开始进行发送
release
消息,一直找到POOL_BOUNDARY
为止 - 一旦发现当前页已经空了,就会去上一个页面进行
pop
,并释放当前页面 - 整个入栈出栈的顺序是采用先进后出,和栈中顺序一样,但不代表着这里说的是真正的栈
pop出栈图示:
通过私有函数打印自动释放池的情况
我们可以通过一个私有函数_objc_autoreleasePoolPrint
来打印分析整个autorelease的过程
objectivec
// 声明内部私有函数,可以调用执行
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool { // r1 = push
Person* person1 = [[[Person alloc] init] autorelease];
Person* person2 = [[[Person alloc] init] autorelease];
@autoreleasepool { // r2 = push()
Person* person3 = [[[Person alloc] init] autorelease];
@autoreleasepool { // r3 = push()
Person* person4 = [[[Person alloc] init] autorelease];
_objc_autoreleasePoolPrint();
} // pop(r3)
} // pop(r2)
// _objc_autoreleasePoolPrint();
} // pop(r1)
}
return 0;
}
打印结果: