1. 概述
AutoReleasePool
:自动释放池
OC
中的一种内存自动回收机制,它可以将加入AutoreleasePool
中的变量release
的时机延迟
简单来说,当创建一个对象,在正常情况下,变量会在超出其作用域时立即release
。如果将其加入到自动释放池中,这个对象并不会立即释放,而会等到runloop
休眠/超出autoreleasepool
作用域之后进行释放
从程序启动到加载完成,主线程对应的
Runloop
会处于休眠状态,等待用户交互来唤醒Runloop
用户每次交互都会启动一次
Runloop
,用于处理用户的所有点击、触摸等事件Runloop
在监听到交互事件后,就会创建自动释放池,并将所有延迟释放的对象添加到自动释放池中在一次完整的
Runloop
结束之前,会向自动释放池中所有对象发送release
消息,然后销毁自动释放池
2. 结构
2.1 使用cpp
文件探索
创建一个Mac
工程,在main.m
中,自动生成以下代码:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello, World!");
}
return 0;
}
生成cpp
文件
clang -rewrite-objc main.m -o main.cpp
打开cpp
文件,来到main
函数
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_jl_d06jlfkj2ws74_5g45kms07m0000gn_T_main_da0d58_mi_0);
}
return 0;
}
autoreleasepool
被注释掉了,但作用域还在- 作用域中生成对
__AtAutoreleasePool
类型声明的代码
找到__AtAutoreleasePool
定义
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
__AtAutoreleasePool
是一个结构体,包含构造函数和析构函数- 结构体声明,触发构造函数,调用
objc_autoreleasePoolPush
函数 - 当结构体出作用域空间,触发析构函数,调用
objc_autoreleasePoolPop
函数
2.2 使用汇编代码探索
打开项目,在main
函数中autoreleasepool
处设置断点,查看汇编代码:
- 调用
objc_autoreleasePoolPush
函数 - 调用
objc_autoreleasePoolPop
函数
进入objc_autoreleasePoolPush
函数
- 源码来自于
libobjc
框架
2.3 源码探索
打开objc4-818.2
源码,找到objc_autoreleasePoolPush
函数
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
- 调用
AutoreleasePoolPage
命名空间下的push
函数
2.3.1 AutoreleasePoolPage
找到AutoreleasePoolPage
的定义,首先看到这样一段注释:
/***********************************************************************
Autorelease pool implementation
A thread's autorelease pool is a stack of pointers.
线程的自动释放池是一个指针堆栈
Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary.
每个指针要么是一个要释放的对象,要么是POOL_BOUNDARY自动释放池边界
A pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is popped, every object hotter than the sentinel is released.
池令牌是指向该池的POOL_BOUNDARY的指针。当池被弹出,每个比哨兵热的对象都被释放
The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary.
堆栈被分成一个双链接的页面列表。根据需要添加和删除页面
Thread-local storage points to the hot page, where newly autoreleased objects are stored.
线程本地存储指向热页,其中存储新自动释放的对象
**********************************************************************/
通过注释我们可以了解到以下几点:
自动释放池和线程有关系
自动释放池是一个存储指针的栈结构
指针要么是一个要释放的对象,要么是
POOL_BOUNDARY
自动释放池边界,俗称:哨兵对象- 哨兵对象的作用:当自动释放池将对象进行
pop
操作时,需要知道边界在哪里,否则会破坏别人的内存空间。而哨兵对象,就是作为边界的标识而存在
- 哨兵对象的作用:当自动释放池将对象进行
自动释放池的栈空间被分成一个双链接结构的页面列表,可添加和删除页面
- 双向链表的特点,一个页中同时存在父节点和子节点。可向前找到父页面,也可向后找到子页面
线程本地存储指向热页,其中存储新自动释放的对象
- 栈原则,先进后出,可以理解为最后一个页面就是热页。里面的对象最后被
push
,最先被pop
- 栈原则,先进后出,可以理解为最后一个页面就是热页。里面的对象最后被
AutoreleasePoolPage
继承于AutoreleasePoolPageData
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
friend struct thread_data_t;
public:
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;
...
}
2.3.2 AutoreleasePoolPageData
找到AutoreleasePoolPageData
的定义
class AutoreleasePoolPage;
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 *child;
uint32_t const depth;
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)
{
}
};
结构体中,包含以下成员变量:
magic
:用来校验AutoreleasePoolPage
的结构是否完整next
:指向最新添加的autoreleased
对象的下一个位置,初始化时执行begin()
thread
:指向当前线程parent
:指向父节点,第一个节点的parent
值为nil
child
:指向子节点,最后一个节点的child
值为nil
depth
:代表深度,从0
开始,往后递增1
hiwat
:代表high water mark
最大入栈数量标记
2.3.3 打印结构
搭建测试项目,关闭ARC
模式
打开main.m
文件,写入以下代码:
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *objc = [[[NSObject alloc] init] autorelease];
_objc_autoreleasePoolPrint();
}
return 0;
}
- 导入
_objc_autoreleasePoolPrint
函数,用于打印自动释放池的结构 - 创建NSObject实例对象,加入自动释放池
- 调用
_objc_autoreleasePoolPrint
函数,打印结构
输出以下内容:
##############
AUTORELEASE POOLS for thread 0x1000ebe00
2 releases pending.
[0x10700b000] ................ PAGE (hot) (cold)
[0x10700b038] ################ POOL 0x10700b038
[0x10700b040] 0x100705f60 NSObject
##############
打印出当前自动释放池所属线程
有
2
个需要释放的对象当前的
Page
信息,占56字节
。因为只有一页,即是冷页面,也是热页面哨兵对象
POOL
NSObject
对象
2.3.4 _objc_autoreleasePoolPrint
官方用于对自动释放池内容调试打印的函数
void
_objc_autoreleasePoolPrint(void)
{
AutoreleasePoolPage::printAll();
}
进入printAll
函数
static void printAll()
{
_objc_inform("##############");
_objc_inform("AUTORELEASE POOLS for thread %p", objc_thread_self());
AutoreleasePoolPage *page;
ptrdiff_t objects = 0;
for (page = coldPage(); page; page = page->child) {
objects += page->next - page->begin();
}
_objc_inform("%llu releases pending.", (unsigned long long)objects);
if (haveEmptyPoolPlaceholder()) {
_objc_inform("[%p] ................ PAGE (placeholder)",
EMPTY_POOL_PLACEHOLDER);
_objc_inform("[%p] ################ POOL (placeholder)",
EMPTY_POOL_PLACEHOLDER);
}
else {
for (page = coldPage(); page; page = page->child) {
page->print();
}
}
_objc_inform("##############");
}
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
__attribute__((noinline, cold))
unsigned sumOfExtraReleases()
{
unsigned sumOfExtraReleases = 0;
for (id *p = begin(); p < next; p++) {
if (*p != POOL_BOUNDARY) {
sumOfExtraReleases += ((AutoreleasePoolEntry *)p)->count;
}
}
return sumOfExtraReleases;
}
#endif
__attribute__((noinline, cold))
static void printHiwat()
{
// Check and propagate high water mark
// Ignore high water marks under 256 to suppress noise.
AutoreleasePoolPage *p = hotPage();
uint32_t mark = p->depth*COUNT + (uint32_t)(p->next - p->begin());
if (mark > p->hiwat + 256) {
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
unsigned sumOfExtraReleases = 0;
#endif
for( ; p; p = p->parent) {
p->unprotect();
p->hiwat = mark;
p->protect();
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
sumOfExtraReleases += p->sumOfExtraReleases();
#endif
}
_objc_inform("POOL HIGHWATER: new high water mark of %u "
"pending releases for thread %p:",
mark, objc_thread_self());
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
if (sumOfExtraReleases > 0) {
_objc_inform("POOL HIGHWATER: extra sequential autoreleases of objects: %u",
sumOfExtraReleases);
}
#endif
void *stack[128];
int count = backtrace(stack, sizeof(stack)/sizeof(stack[0]));
char **sym = backtrace_symbols(stack, count);
for (int i = 0; i < count; i++) {
_objc_inform("POOL HIGHWATER: %s", sym[i]);
}
free(sym);
}
}
#undef POOL_BOUNDARY
};
- 按照自动释放池的结构,通过双向链表遍历
page
,依次读取page
中的内容并进行打印
3. 对象压栈
进入objc_autoreleasePoolPush
函数
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
进入AutoreleasePoolPage
命名空间下的push
函数
static inline void *push()
{
id *dest;
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
DebugPoolAllocation
:当自动释放池按顺序弹出时停止,并允许堆调试器跟踪自动释放池- 不存在,调用
autoreleaseNewPage
函数,从一个新的池页开始创建 - 否则,调用
autoreleaseFast
函数,将哨兵对象压栈
3.1 autoreleaseFast
进入autoreleaseFast
函数
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
如果存在
page
,并且没有存满,调用add
函数如果存在
page
,但存储已满,调用autoreleaseFullPage
函数否则,不存在
page
,调用autoreleaseNoPage
函数
3.1.1 autoreleaseNoPage
进入autoreleaseNoPage
函数
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;
...
// 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.
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
return page->add(obj);
}
- 调用
AutoreleasePoolPage
构造函数,创建新页 - 设置为热页面
pushExtraBoundary
为YES
,哨兵对象压栈- 对象压栈
进入AutoreleasePoolPage
构造函数
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),
objc_thread_self(),
newParent,
newParent ? 1+newParent->depth : 0,
newParent ? newParent->hiwat : 0)
{
if (objc::PageCountWarning != -1) {
checkTooMuchAutorelease();
}
if (parent) {
parent->check();
ASSERT(!parent->child);
parent->unprotect();
parent->child = this;
parent->protect();
}
protect();
}
- 通过父类
AutoreleasePoolPageData
进行初始化 begin
:获取对象压栈的起始位置objc_thread_self
:通过tls
获取当前线程- 链接双向链表
进入begin
函数
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
sizeof(*this)
:大小取决于自身结构体中的成员变量- 返回对象可压栈的真正开始地址,在成员变量以下
进入AutoreleasePoolPageData
的定义
struct AutoreleasePoolPageData
{
...
magic_t const magic;
__unsafe_unretained id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
...
};
magic
:16字节
next
、thread
、parent
、child
:各占8字节
depth
、hiwat
:各占4字节
- 共占
56字节
进入magic_t
的定义
struct magic_t {
static const uint32_t M0 = 0xA1A1A1A1;
# define M1 "AUTORELEASE!"
static const size_t M1_len = 12;
uint32_t m[4];
...
# undef M1
};
- 结构体的创建在堆区申请内存,而静态成员存储在静态区,不占结构体大小
16字节
来自于uint32_t
数组
进入objc_thread_self
函数
static inline pthread_t objc_thread_self()
{
return (pthread_t)tls_get_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF);
}
- 通过
tls
获取当前线程
3.1.2 autoreleaseFullPage
进入autoreleaseFullPage
函数
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);
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
}
- 遍历链表,找到最后一个空白的子页面
- 对其进行创建新页
- 设置为热页面
- 添加对象
形成以下数据结构:
3.1.3 add
进入add
函数
id *add(id obj)
{
ASSERT(!full());
unprotect();
id *ret;
...
ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
// Make sure obj fits in the bits available for it
ASSERT(((AutoreleasePoolEntry *)ret)->ptr == (uintptr_t)obj);
#endif
done:
protect();
return ret;
}
- 使用
*next++
进行内存平移 - 将对象压栈
3.2 autoreleaseNewPage
进入autoreleaseNewPage
函数
id *autoreleaseNewPage(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page) return autoreleaseFullPage(obj, page);
else return autoreleaseNoPage(obj);
}
- 获取热页面
- 存在,调用
autoreleaseFullPage
函数 - 否则,不存在
page
,调用autoreleaseNoPage
函数
4. autorelease
进入objc_autorelease
函数
id
objc_autorelease(id obj)
{
if (obj->isTaggedPointerOrNil()) return obj;
return obj->autorelease();
}
- 当前对象为
TaggedPointer
或nil
,直接返回 - 否则,调用对象的
autorelease
函数
进入autorelease
函数
inline id
objc_object::autorelease()
{
ASSERT(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
return rootAutorelease();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));
}
- 如果是自定义类,调用
rootAutorelease
函数
进入rootAutorelease
→rootAutorelease2
→AutoreleasePoolPage::autorelease
函数
inline id
objc_object::rootAutorelease()
{
if (isTaggedPointer()) return (id)this;
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
return rootAutorelease2();
}
id
objc_object::rootAutorelease2()
{
ASSERT(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
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
函数,对象压栈
5. 池页容量
自动释放池采用分页的方式存储对象,因为对象在频繁压栈和出栈的过程中,产生异常,值会影响当前页面,不会影响到整个自动释放池
并且自动释放池分页管理,每页之前的地址可以不连续,它们可以使用双向链表找到父页面和子页面。如果所有对象都使用一页存储,为了保证地址的连续性,每次扩容会相对繁琐和耗时
int main(int argc, const char * argv[]) {
@autoreleasepool {
for (int i = 0; i < 505; i++) {
NSObject *objc = [[[NSObject alloc] init] autorelease];
}
_objc_autoreleasePoolPrint();
}
return 0;
}
-------------------------
objc[1804]: ##############
objc[1804]: AUTORELEASE POOLS for thread 0x1000ebe00
objc[1804]: 506 releases pending.
objc[1804]: [0x10200c000] ................ PAGE (full) (cold)
objc[1804]: [0x10200c038] ################ POOL 0x10200c038
objc[1804]: [0x10200c040] 0x100638420 NSObject
objc[1804]: [0x10200c048] 0x100637a40 NSObject
objc[1804]: [0x10200c050] 0x100636970 NSObject
...
objc[1804]: [0x100809000] ................ PAGE (hot)
objc[1804]: [0x100809038] 0x10063a0b0 NSObject
objc[1804]: ##############
将
505
个NSObject
对象循环加入自动释放池,当存储504
个对象时,池页已满。第505
个对象创建新池页存储一页的容量:
504 * 8 = 4032
,加上56字节
成员变量和8字节
哨兵对象,共计4096
字节每一页都存在
56字节
的成员变量一个自动释放池,只会压栈一个哨兵对象
在源码中查看
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
friend struct thread_data_t;
public:
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
...
}
来到PAGE_MIN_SIZE
的定义
#define PAGE_MIN_SHIFT 12
#define PAGE_MIN_SIZE (1 << PAGE_MIN_SHIFT)
1
左移12
位,相当于2 ^ 12 = 4096
6. 对象出栈
进入objc_autoreleasePoolPop
函数
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
进入pop
函数
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.
//不存在热页面,将标记设置为nil
return setHotPage(nil);
}
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
//存在热页面,通过双向链表循环向上找到最冷页面
page = coldPage();
//将token设置为起始位置
token = page->begin();
} else {
//获取token所在的页
page = pageForPointer(token);
}
//赋值给stop
stop = (id *)token;
//当前位置不是哨兵对象
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
//最冷页面的起始可能不是POOL_BOUNDARY:
//1. 弹出顶级池,保留冷页面
//2. 对象在没有池的情况下被自动释放
} 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);
}
6.1 popPage
进入popPage
函数
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();
//当前页中对象出栈,到stop位置停止
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
//特殊情况:删除所有的pop
//销毁当前页面
page->kill();
//将热页面标记设置为nil
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();
}
}
}
6.2 releaseUntil
进入releaseUntil
函数
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 (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->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) {
#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
}
6.3 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) {
//将子页面设置为nil
page->unprotect();
page->child = nil;
page->protect();
}
//销毁子页面
delete deathptr;
//遍历销毁到this为止
} while (deathptr != this);
}
7. 嵌套使用
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *objc = [[[NSObject alloc] init] autorelease];
@autoreleasepool {
NSObject *objc = [[[NSObject alloc] init] autorelease];
}
_objc_autoreleasePoolPrint();
}
return 0;
}
-------------------------
objc[2511]: ##############
objc[2511]: AUTORELEASE POOLS for thread 0x1000ebe00
objc[2511]: 4 releases pending.
objc[2511]: [0x10680d000] ................ PAGE (hot) (cold)
objc[2511]: [0x10680d038] ################ POOL 0x10680d038
objc[2511]: [0x10680d040] 0x101370c40 NSObject
objc[2511]: [0x10680d048] ################ POOL 0x10680d048
objc[2511]: [0x10680d050] 0x101365fb0 NSObject
objc[2511]: ##############
- 可以嵌套使用
线程的自动释放池是一个指针堆栈,当嵌套使用时,添加好各自堆栈的哨兵对象。出栈时,先释放内部,再释放外部
8. ARC模式
将项目设置为ARC
模式
创建LGPerson
类
#import <Foundation/Foundation.h>
@interface LGPerson : NSObject
+ (LGPerson *)person;
@end
@implementation LGPerson
+ (LGPerson *)person{
LGPerson *p = [[LGPerson alloc] init];
return p;
}
@end
在main
函数中,写入以下代码:
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *p1 = [[LGPerson alloc] init];
LGPerson *p2 = [LGPerson person];
_objc_autoreleasePoolPrint();
}
return 0;
}
-------------------------
objc[2699]: ##############
objc[2699]: AUTORELEASE POOLS for thread 0x1000ebe00
objc[2699]: 2 releases pending.
objc[2699]: [0x10200e000] ................ PAGE (hot) (cold)
objc[2699]: [0x10200e038] ################ POOL 0x10200e038
objc[2699]: [0x10200e040] 0x1013658b0 LGPerson
objc[2699]: ##############
- 只有
p2
对象压栈到自动释放池
ARC
模式,使用alloc
、new
、copy
、mutableCopy
前缀开头的方法进行对象创建,不会加入到自动释放池。它们的空间开辟由开发者申请,释放也由开发者进行管理
临时变量的释放:
正常情况下,超出其作用域就会立即释放
加入自动释放池,会延迟释放。当
Runloop
休眠或超出autoreleasepool
作用域之后释放
9. 与线程、Runloop的关系
参见官方文档:NSAutoreleasePool
9.1 与线程的关系
每个线程(包括主线程)维护自己的对象堆栈。随着新池的创建,它们被添加到堆栈的顶部。当池被释放时,它们会从堆栈中移除
autoreleased
对象被放置在当前线程的顶部自动释放池中。当一个线程终止时,它会自动清空所有与其关联的自动释放池
9.2 与Runloop
的关系
主程序在事件循环的每个循环开始时在主线程上创建一个自动释放池
并在结束时将其排空,从而释放在处理事件时生成的任何自动释放对象
总结
概述:
AutoReleasePool
:自动释放池OC
中的一种内存自动回收机制,它可以将加入AutoreleasePool
中的变量release
的时机延迟Runloop
在监听到交互事件后,就会创建自动释放池,并将所有延迟释放的对象添加到自动释放池中在一次完整的
Runloop
结束之前,会向自动释放池中所有对象发送release
消息,然后销毁自动释放池
结构:
自动释放池的压栈和出栈,通过结构体的构造函数和析构函数触发
压栈:调用
objc_autoreleasePoolPush
函数出栈:调用
objc_autoreleasePoolPop
函数
特点:
自动释放池和线程有关系
自动释放池是一个存储指针的栈结构
指针要么是一个要释放的对象,要么是
POOL_BOUNDARY
自动释放池边界,俗称:哨兵对象哨兵对象的作用:当自动释放池将对象进行
pop
操作时,需要知道边界在哪里,否则会破坏别人的内存空间。而哨兵对象,就是作为边界的标识而存在自动释放池的栈空间被分成一个双链接结构的页面列表,可添加和删除页面
双向链表的特别,一个页中同时存在父节点和子节点。可向前找到父页面,也可向后找到子页面
线程本地存储指向热页,其中存储新自动释放的对象
栈原则,先进后出,可以理解为最后一个页面就是热页。里面的对象最后被
push
,最先被pop
容量:
- 池页大小为
4096字节
,每一页都包含56字节
的成员变量,但一个自动释放池中,只会压栈一个哨兵对象,占8字节
原理:
自动释放池的本质是
__AtAutoreleasePool
结构体,包含构造函数和析构函数结构体声明,触发构造函数,调用
objc_autoreleasePoolPush
函数,本质是对象压栈的push
方法当结构体出作用域空间,触发析构函数,调用
objc_autoreleasePoolPop
函数,本质是对象出栈的pop
方法
对象压栈
如果存在
page
,并且没有存满,调用add
函数使用
*next++
进行内存平移将对象压栈
如果存在
page
,但存储已满,调用autoreleaseFullPage
函数遍历链表,找到最后一个空白的子页面
对其进行创建新页
设置为热页面
添加对象
否则,不存在
page
,调用autoreleaseNoPage
函数通过父类
AutoreleasePoolPageData
进行初始化begin
:获取对象压栈的起始位置objc_thread_self
:通过tls
获取当前线程链接双向链表
设置为热页面
pushExtraBoundary
为YES
,哨兵对象压栈对象压栈
对象出栈
调用
popPage
函数,传入stop
为哨兵对象的位置当前页中对象出栈,到
stop
位置停止调用
kill
函数,销毁当前页面
嵌套使用:
- 线程的自动释放池是一个指针堆栈,当嵌套使用时,添加好各自堆栈的哨兵对象。出栈时,先释放内部,再释放外部
ARC
模式:
ARC
模式,使用alloc
、new
、copy
、mutableCopy
前缀开头的方法进行对象创建,不会加入到自动释放池。它们的空间开辟由开发者申请,释放也由开发者进行管理
与线程的关系:
每个线程(包括主线程)维护自己的对象堆栈。随着新池的创建,它们被添加到堆栈的顶部。当池被释放时,它们会从堆栈中移除
autoreleased
对象被放置在当前线程的顶部自动释放池中。当一个线程终止时,它会自动清空所有与其关联的自动释放池
与Runloop
的关系:
主程序在事件循环的每个循环开始时在主线程上创建一个自动释放池
并在结束时将其排空,从而释放在处理事件时生成的任何自动释放对象