有关内存管理的相关优化方案和引用计数的相关原理,咱们已经了解,本章来说解在内存管理中的另外一个方案Autoreleasepoolbash
传送门☞iOS底层学习 - 内存管理以内存管理方案app
传送门☞iOS底层学习 - 内存管理之weak原理探究框架
经过以前章节的学习,咱们知道在ARC
下,LLVM编译器会自动帮咱们生产retain
、release
和autorelease
等代码,减小了在MRC
下的工做量。调用autorelease
会将该对象添加进自动释放池中,它会在一个恰当的时刻自动给对象调用release
,因此autorelease
至关于延迟了对象的释放。less
可是在ARC
下,autorelease
方法已被禁用,咱们可使用__autoreleasing
修饰符修饰对象将对象注册到自动释放池中。ide
MRC
下,可使用NSAutoreleasePool
或者@autoreleasepool
。建议使用@autoreleasepool
,苹果说它比NSAutoreleasePool
快大约六倍。NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Code benefitting from a local autorelease pool.
[pool release];
复制代码
NSAutoreleasePool
类建立自动释放池,只能使用@autoreleasepool。
@autoreleasepool {
// Code benefitting from a local autorelease pool.
}
复制代码
The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event. If you use the Application Kit, you therefore typically don’t have to create your own pools. If your application creates a lot of temporary autoreleased objects within the event loop, however, it may be beneficial to create “local” autorelease pools to help to minimize the peak memory footprint.函数
以上是苹果对自动释放池的一段介绍,其意思为:AppKit
和 UIKit
框架在事件循环(RunLoop
)的每次循环开始时,在主线程建立一个自动释放池,并在每次循环结束时销毁它,在销毁时释放自动释放池中的全部autorelease
对象。一般状况下咱们不须要手动建立自动释放池,可是若是咱们在循环中建立了不少临时的autorelease
对象,则手动建立自动释放池来管理这些对象能够很大程度地减小内存峰值。工具
咱们知道在main
函数中,会建立一个@autoreleasepool {}
对象,那么其底层的结构是怎样的呢?oop
int main(int argc, const char * argv[]) {
@autoreleasepool {}
return 0;
}
复制代码
咱们仍是使用clang -rewrite-objc main.m
命令,转换为C++代码查看。经过如下代码,咱们能够发现转换后@autoreleasepool
主要作了如下几点:post
@autoreleasepool
底层是建立了一个__AtAutoreleasePool
结构体对象;__AtAutoreleasePool
结构体时会在构造函数中调用objc_autoreleasePoolPush()
函数,并返回一个atautoreleasepoolobj
(POOL_BOUNDARY存放的内存地址,下面会讲到);__AtAutoreleasePool
结构体时会在析构函数中调用objc_autoreleasePoolPop()
函数,并将atautoreleasepoolobj
传入。struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; }
return 0;
}
复制代码
首先来看AutoreleasePoolPage
的相关源码,其几个成员变量的含义以下:学习
AutoreleasePoolPage
的结构是否完整。AutoreleasePoolPage
中下一个为空的内存地址(新来的对象会存储到next处),初始化时指向begin()。AutoreleasePoolPage
属于一个线程,一个线程中能够有多个AutoreleasePoolPage
)。PAGE_MAX_SIZE
,4096个字节,其中56
个字节用来存储本身的变量,剩下的4040
个字节用来存储要释放的对象,也就是最多505
个对象。POOL_SENTINEL
,称为哨兵对象或者边界对象;POOL_BOUNDARY
用来区分不一样的自动释放池,以解决自动释放池嵌套的问题push()
方法将一个POOL_BOUNDARY
入栈,并返回其存放的内存地址;autorelease
对象时,将autorelease
对象的内存地址入栈,它们前面至少有一个POOL_BOUNDARY
;pop()
方法并传入一个POOL_BOUNDARY
,会从自动释放池中最后一个对象开始,依次给它们发送release
消息,直到遇到这个POOL_BOUNDARY
。class AutoreleasePoolPage;
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;
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)
{
}
};
----------------------------------------------------------------------------------
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);
// EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is
// pushed and it has never contained any objects. This saves memory
// when the top level (i.e. libdispatch) pushes and pops pools but
// never uses them.
# define EMPTY_POOL_PLACEHOLDER ((id*)1)
# define POOL_BOUNDARY nil
// SIZE-sizeof(*this) bytes of contents follow
......
}
复制代码
/***********************************************************************
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.
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.
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(自动释放池边界)
一个 pool token 就是这个 pool 所对应的 POOL_BOUNDARY 的内存地址。当这个 pool 被 pop 的时候,全部内存地址在 pool token 以后的对象都会被 release。 这个堆栈被划分红了一个以 page 为结点的双向链表。pages 会在必要的时候动态地增长或删除。
Thread-local storage(线程局部存储)指向 hot page ,即最新添加的 autoreleased 对象所在的那个 page 。
经过上面对成员变量的解析和上方官方的注释,咱们能够知道AutoreleasePoolPage
底层结构以下:
AutoreleasePoolPage
是以栈为结点经过双向链表的形式组合而成;遵循先进后出规则,整个自动释放池由一系列的AutoreleasePoolPage
组成的,而AutoreleasePoolPage
是以双向链表的形式链接起来。AutoreleasePoolPage
对象占用4096
字节内存,其中56
个字节用来存放它内部的成员变量,剩下的空间(4040个字节)用来存放autorelease对象的地址。要注意的是第一页只有504个对象,由于在建立page的时候会在next
的位置插入1个POOL_SENTINEL
。POOL_BOUNDARY
为哨兵对象,入栈时插入,出栈时释放对象到此传入的哨兵对象该图表示AutoreleasePoolPage
的双向列表结构
AutoreleasePoolPage
的双向列表和栈结构
首先咱们看objc_autoreleasePoolPush
的源码,发现其内部就是调用了AutoreleasePoolPage
的push()
方法。
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
复制代码
来到AutoreleasePoolPage
内部的push()
方法,其中slowpath
表示不多会走到,是底部的容错处理,因此最终会走到autoreleaseFast
方法中
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;
}
复制代码
查看autoreleaseFast
源码,先是调用了hotPage()
,hotPage()
方法就是用来得到新建立的未满的Page。其内部主要是判断逻辑:
page->add(obj)
将 autorelease 对象入栈,即添加到当前 Page 中autoreleaseFullPage
,建立一个新的 Page,并将 autorelease 对象添加进去static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage(); // 双向链表中的最后一个 Page
if (page && !page->full()) {// 若是当前 Page 存在且未满
return page->add(obj); // 将 autorelease 对象入栈,即添加到当前 Page 中;
} else if (page) { // 若是当前 Page 存在但已满
return autoreleaseFullPage(obj, page); // 建立一个新的 Page,并将 autorelease 对象添加进去
} else {// 若是当前 Page 不存在,即还没建立过 Page
return autoreleaseNoPage(obj); // 建立第一个 Page,并将 autorelease 对象添加进去
}
}
复制代码
首先咱们来看一下,如何判断当前page是不是满状态的。
begin
的地址为:Page本身的地址+Page对象的大小56个字节;end
的地址为:Page本身的地址+4096个字节;empty
:判断Page是否为空的条件是next地址是否是等于begin;full
:判断Page是否已满的条件是next地址是否是等于end(栈顶)。咱们知道next
指向的是下一个AutoreleasePoolPage
中下一个为空的内存地址,新对象会存在next
,若是此时next
指向end
则表明当前AutoreleasePoolPage
已满。
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
bool empty() {
return next == begin();
}
bool full() {
return next == end();
}
复制代码
当page没有存满时,会调用此方法,内部的原理很是简单,就是一个压栈的操做,并将next
指针指向这个对象的下一个位置,而后将该对象的位置返回。
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
复制代码
若是当前 Page 存在但已满,会调用此方法。其内部实现的主要方法就是一个do..while
循环,主要实现了一下的逻辑
page->child
page->full()
new
一个AutoreleasePoolPage
setHotPage(page)
将当前页设置为活跃page->add
压栈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);
}
复制代码
当没有page时,会走到此方法,其主要逻辑以下:
setEmptyPoolPlaceholder()
生成一个占位符,表示一个空的自动释放池Page
,设置它为hotPage
POOL_BOUNDARY
添加进Page中,并返回POOL_BOUNDARY
的下一个位置。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;
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;
}
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;
}
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.
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
return page->add(obj);
}
复制代码
看完对象入栈的实现,咱们再来看一下出栈的实现。
首先pop的入参token
即为POOL_BOUNDARY
对应在Page中的地址。当销毁自动释放池时,会从从自动释放池的中的最后一个入栈的autorelease对象开始,依次给它们发送一条release
消息,直到遇到这个POOL_BOUNDARY
,具体的步骤以下:
token
是否是EMPTY_POOL_PLACEHOLDER
,是的话就清空这个自动释放池pageForPointer(token)
拿到token
所在的Page
page->releaseUntil(stop)
将自动释放池中的autorelease
对象所有释放,传参sto
p即为POOL_BOUNDARY
的地址static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
if (hotPage()) {
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
pop(coldPage()->begin());
} else {
// Pool was never used. Clear the placeholder.
setHotPage(nil);
}
return;
}
page = pageForPointer(token);
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
} else {
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
return badPop(token);
}
}
if (PrintPoolHiwat) printHiwat();
page->releaseUntil(stop);
// memory: delete empty children
if (DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (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();
}
}
}
复制代码
该方法,主要是经过token来拿到当前所在的page。主要实现原理是将指针token与页面的大小(4096)取模,能够获得当前指针的偏移量。而后将指针的地址减偏移量即可以获得首地址。即该page的地址
static AutoreleasePoolPage *pageForPointer(uintptr_t p)
{
AutoreleasePoolPage *result;
uintptr_t offset = p % SIZE;
ASSERT(offset >= sizeof(AutoreleasePoolPage));
result = (AutoreleasePoolPage *)(p - offset);
result->fastcheck();
return result;
}
复制代码
pop()方法中释放autorelease对象的过程在releaseUntil()方法中,下面来看一下这个方法的实现:
releaseUntil()
方法其实就是经过一个while
循环hotPage
开始,一直释放,直到stop
,即传入的POOL_BOUNDARY
hotPage
void releaseUntil(id *stop)
{
// Not recursive: we donot want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
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 canot prove it
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
id obj = *--page->next; // next指针是指向最后一个对象的后一个位置,因此须要先减1
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
if (obj != POOL_BOUNDARY) {
objc_release(obj);
}
}
setHotPage(this);
#if DEBUG
// we expect any children to be completely empty
for (AutoreleasePoolPage *page = child; page; page = page->child) {
assert(page->empty());
}
#endif
}
复制代码
kill
方法删除双向链表中的每个的page,找到当前page
的 child
方向尾部 page
,而后反向挨着释放而且把其parent
节点的 child
指针置空。
void kill()
{
// Not recursive: we donot want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
AutoreleasePoolPage *page = this;
// 找到链表最末尾的page
while (page->child) page = page->child;
AutoreleasePoolPage *deathptr;
// 循环删除每个page
do {
deathptr = page;
page = page->parent;
if (page) {
page->unprotect();
page->child = nil;
page->protect();
}
delete deathptr;
} while (deathptr != this);
}
复制代码
准备:
extern void _objc_autoreleasePoolPrint(void);
方法来打印autoreleasePool的相关信息int main(int argc, const char * argv[]) {
_objc_autoreleasePoolPrint(); // print1
@autoreleasepool { //r1 = push()
_objc_autoreleasePoolPrint(); // print2
NSObject *p1 = [[[NSObject alloc] init] autorelease];
NSObject *p2 = [[[NSObject alloc] init] autorelease];
_objc_autoreleasePoolPrint(); // print3
@autoreleasepool { //r2 = push()
NSObject *p3 = [[[NSObject alloc] init] autorelease];
_objc_autoreleasePoolPrint(); // print4
@autoreleasepool { //r3 = push()
NSObject *p4 = [[[NSObject alloc] init] autorelease];
_objc_autoreleasePoolPrint(); // print5
} //pop(r3)
_objc_autoreleasePoolPrint(); // print6
} //pop(r2)
_objc_autoreleasePoolPrint(); // print7
} //pop(r1)
_objc_autoreleasePoolPrint(); // print8
return 0;
}
复制代码
打印结果过以下,经过打印结果,咱们能够印证上面原理的探索,其主要的进出栈流程以下图所示,且做用域只在@autoreleasepool {}
之间,超过以后就所有调用pop释放
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 0 releases pending.
objc[12943]: ##############
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 0 releases pending.
objc[12943]: [0x1] ................ PAGE (placeholder)
objc[12943]: [0x1] ################ POOL (placeholder)
objc[12943]: ##############
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 3 releases pending.
objc[12943]: [0x7f924480b000] ................ PAGE (hot) (cold)
objc[12943]: [0x7f924480b038] ################ POOL 0x7f924480b038
objc[12943]: [0x7f924480b040] 0x600001b34070 NSObject
objc[12943]: [0x7f924480b048] 0x600001b34080 NSObject
objc[12943]: ##############
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 5 releases pending.
objc[12943]: [0x7f924480b000] ................ PAGE (hot) (cold)
objc[12943]: [0x7f924480b038] ################ POOL 0x7f924480b038
objc[12943]: [0x7f924480b040] 0x600001b34070 NSObject
objc[12943]: [0x7f924480b048] 0x600001b34080 NSObject
objc[12943]: [0x7f924480b050] ################ POOL 0x7f924480b050
objc[12943]: [0x7f924480b058] 0x600001b34090 NSObject
objc[12943]: ##############
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 7 releases pending.
objc[12943]: [0x7f924480b000] ................ PAGE (hot) (cold)
objc[12943]: [0x7f924480b038] ################ POOL 0x7f924480b038
objc[12943]: [0x7f924480b040] 0x600001b34070 NSObject
objc[12943]: [0x7f924480b048] 0x600001b34080 NSObject
objc[12943]: [0x7f924480b050] ################ POOL 0x7f924480b050
objc[12943]: [0x7f924480b058] 0x600001b34090 NSObject
objc[12943]: [0x7f924480b060] ################ POOL 0x7f924480b060
objc[12943]: [0x7f924480b068] 0x600001b2c030 NSObject
objc[12943]: ##############
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 5 releases pending.
objc[12943]: [0x7f924480b000] ................ PAGE (hot) (cold)
objc[12943]: [0x7f924480b038] ################ POOL 0x7f924480b038
objc[12943]: [0x7f924480b040] 0x600001b34070 NSObject
objc[12943]: [0x7f924480b048] 0x600001b34080 NSObject
objc[12943]: [0x7f924480b050] ################ POOL 0x7f924480b050
objc[12943]: [0x7f924480b058] 0x600001b34090 NSObject
objc[12943]: ##############
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 3 releases pending.
objc[12943]: [0x7f924480b000] ................ PAGE (hot) (cold)
objc[12943]: [0x7f924480b038] ################ POOL 0x7f924480b038
objc[12943]: [0x7f924480b040] 0x600001b34070 NSObject
objc[12943]: [0x7f924480b048] 0x600001b34080 NSObject
objc[12943]: ##############
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 0 releases pending.
objc[12943]: [0x7f924480b000] ................ PAGE (hot) (cold)
objc[12943]: ##############
复制代码
int main(int argc, const char * argv[]) {
@autoreleasepool { //r1 = push()
for (int i = 0; i < 600; i++) {
NSObject *p = [[[NSObject alloc] init] autorelease];
}
@autoreleasepool { //r2 = push()
for (int i = 0; i < 500; i++) {
NSObject *p = [[[NSObject alloc] init] autorelease];
}
@autoreleasepool { //r3 = push()
for (int i = 0; i < 200; i++) {
NSObject *p = [[[NSObject alloc] init] autorelease];
}
_objc_autoreleasePoolPrint();
} //pop(r3)
} //pop(r2)
} //pop(r1)
return 0;
}
复制代码
能够看到打印结果以下:根据原理的探究,咱们知道每一个page除了第一页是504个对象外,其余最多存储505个对象,当一个page满了时候,会建立一个新的page,而且每一个page之间是以栈
为结点经过双向链表
的形式组合而成。其主要流程以下图所示
objc[69731]: ##############
objc[69731]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[69731]: 1303 releases pending. //当前自动释放池中有1303个对象(3个POOL_BOUNDARY和1300个NSObject实例)
objc[69731]: [0x100806000] ................ PAGE (full) (cold) /* 第一个PAGE,full表明已满,cold表明coldPage */
objc[69731]: [0x100806038] ################ POOL 0x100806038 //POOL_BOUNDARY
objc[69731]: [0x100806040] 0x10182a040 NSObject //p1
objc[69731]: [0x100806048] ..................... //...
objc[69731]: [0x100806ff8] 0x101824e40 NSObject //p504
objc[69731]: [0x102806000] ................ PAGE (full) /* 第二个PAGE */
objc[69731]: [0x102806038] 0x101824e50 NSObject //p505
objc[69731]: [0x102806040] ..................... //...
objc[69731]: [0x102806330] 0x101825440 NSObject //p600
objc[69731]: [0x102806338] ################ POOL 0x102806338 //POOL_BOUNDARY
objc[69731]: [0x102806340] 0x101825450 NSObject //p601
objc[69731]: [0x102806348] ..................... //...
objc[69731]: [0x1028067e0] 0x101825d90 NSObject //p1008
objc[69731]: [0x102804000] ................ PAGE (hot) /* 第三个PAGE,hot表明hotPage */
objc[69731]: [0x102804038] 0x101826dd0 NSObject //p1009
objc[69731]: [0x102804040] ..................... //...
objc[69731]: [0x102804310] 0x101827380 NSObject //p1100
objc[69731]: [0x102804318] ################ POOL 0x102804318 //POOL_BOUNDARY
objc[69731]: [0x102804320] 0x101827390 NSObject //p1101
objc[69731]: [0x102804328] ..................... //...
objc[69731]: [0x102804958] 0x10182b160 NSObject //p1300
objc[69731]: ##############
复制代码
关于RunLoop的相关知识,能够查看文章☞iOS底层学习 - 深刻RunLoop
其中主要的的RunLoop运行流程以下图所示
并且经过打印[NSRunLoop currentRunLoop]
,能够发现其中有_wrapRunLoopWithAutoreleasePoolHandler()
表明的相关AutoreleasePool的回调。
<CFRunLoopObserver 0x6000024246e0 [0x7fff8062ce20]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff48c1235c), context = <CFArray 0x600001b7afd0 [0x7fff8062ce20]>{type = mutable-small, count = 1, values = (0 : <0x7fc18f80e038>)}}
<CFRunLoopObserver 0x600002424640 [0x7fff8062ce20]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff48c1235c), context = <CFArray 0x600001b7afd0 [0x7fff8062ce20]>{type = mutable-small, count = 1, values = (0 : <0x7fc18f80e038>)}}
复制代码
那么,RunLoop和AutoreleasePool的主要关系以下
kCFRunLoopEntry
:在即将进入RunLoop时,会自动建立一个__AtAutoreleasePool
结构体对象,并调用objc_autoreleasePoolPush()
函数。kCFRunLoopBeforeWaiting
:在RunLoop即将休眠时,会自动销毁一个__AtAutoreleasePool
对象,调用objc_autoreleasePoolPop()
。而后建立一个新的__AtAutoreleasePool对象
,并调用objc_autoreleasePoolPush()
。kCFRunLoopBeforeExi
t,在即将退出RunLoop时,会自动销毁最后一个建立的__AtAutoreleasePool
对象,并调用objc_autoreleasePoolPop()
。了解了他们之间的关系,咱们能够经过main函数,来分析一下
// Xcode 11
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
--------------------------------------------------------------------------------
// Xcode 旧版本
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
复制代码
咱们知道@autoreleasepool {}
的做用域只在其大括号之间,并且UIApplicationMain
主线程会建立主RunLoop,经过上面的探究,咱们知道在建立RunLoop的时候,也会对应的建立AutoreleasePool
。其中使用autorelease
修饰的对象都会添加到RunLoop建立的自动释放池中。
因此Xcode 11和以前版本的区别,主要就是Xcode 11将@autoreleasepool {}
提早,这能够保证@autoreleasepool
中的autorelease
对象在程序启动后当即释放。而以前的版本是在主线程RunLoop建立的自动释放池的外层的,意味着程序结束后main函数中的@autoreleasepool
中的autorelease
对象才会释放。
在平时的开发中,咱们通常是不须要使用@autoreleasepool{}
的,可是如下几种状况可使用
@autoreleasepool
在下一次迭代以前处理这些对象。在循环中使用@autoreleasepool有助于减小应用程序的最大内存占用。使用__autorelease
修饰的对象,会被系统自动加入RunLoop建立的自动释放池中,随RunLoop生命周期释放。
release
,保证了对象的延迟释放AutoreleasePoolPage
组成的,而AutoreleasePoolPage
是以双向链表的形式链接起来。objc_autoreleasePoolPush()
来入栈,调用objc_autoreleasePoolPop()
来出栈POOL_BOUNDARY
哨兵对象来做为出入栈的标志位
push()
方法将一个POOL_BOUNDARY
入栈,并返回其存放的内存地址;autorelease对象
时,将autorelease对象的内存地址入栈,它们前面至少有一个POOL_BOUNDARY
;pop()
方法并传入一个POOL_BOUNDARY
,会从自动释放池中最后一个对象开始,依次给它们发送release
消息,直到遇到这个POOL_BOUNDARY
。4096
字节内存,其中56个
字节用来存放它内部的成员变量,剩下的空间(4040个字节
)用来存放autorelease对象的地址。要注意的是第一页只有504个对象
,由于在建立page的时候会在next的位置插入1个POOL_BOUNDARY。page->add(obj)
,将 autorelease 对象入栈,并将next指针指向这个对象的下一个位置,而后将该对象的位置返回autoreleaseFullPage
,循环查找page->child并判断是否已满,都已满则建立新的AutoreleasePoolPage,并将 autorelease 对象入栈,设置HotPageautoreleaseNoPage
,先会判断是否有空的自动释放池存在并生成占位符,而后建立一个新page并设置HotPage,依次插入POOL_BOUNDARY和autorelease 对象入栈kCFRunLoopEntry
:在即将进入RunLoop时,会自动建立一个__AtAutoreleasePool结构体对象,并调用objc_autoreleasePoolPush()函数。kCFRunLoopBeforeWaiting
:在RunLoop即将休眠时,会自动销毁一个__AtAutoreleasePool对象,调用objc_autoreleasePoolPop()。而后建立一个新的__AtAutoreleasePool对象,并调用objc_autoreleasePoolPush()。kCFRunLoopBeforeExit
,在即将退出RunLoop时,会自动销毁最后一个建立的__AtAutoreleasePool对象,并调用objc_autoreleasePoolPop()。