这里只是简单的串一串自己脑海里的知识。所以就简单点儿,引出今天的主题就行了。看如下代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
很简单,很基本的main函数。经过使用命令:
clang -rewrite-objc -framework Foundation main.m -o MainClang.cpp
可以变成c++,进行查看底层的入口。编译后如下:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ {
__AtAutoreleasePool __autoreleasepool; // 这是重点
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hk_zhxz0gtj7z1djqcl5mbjdpd00000gn_T_main_76dea1_mi_0);
}
return 0;
}
__AtAutoreleasePool __autoreleasepool
就是重点。
__AtAutoreleasePool
定义如下(我也不知道该叫做类还是结构体,因为在C++里struct和其他语言中是不一样的):
struct __AtAutoreleasePool {
__AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush();
// 这里返回的要么是对象的储存地址,要么是一个标记EMPTY_POOL_PLACEHOLDER,用来表示当前的Page是空的
}
~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
在该结构体中,就只有三相:构造函数、析构函数和一个成员变量。
-
__AtAutoreleasePool()
这是入口函数,可以发现在其中就做了一件事情:push -
~__AtAutoreleasePool()
这个是析构函数,用于在对象释放的时候,也只干了一件事情:pop -
objc_autoreleasePoolPush和objc_autoreleasePoolPop
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
在这两个里面都不约而同的提到了一个类:AutoreleasePoolPage,而该类才是自动释放池的主角,可以说有了它,才有了自动释放池的一切。
- AutoreleasePoolPage
简单来说,是一个双向列表,它维护着一个用来管理需要自动释放的变量的栈,因为每次开辟的Page大小都是有限的,所以当当前page空间不足的时候,就会重新创建一个Page。
注意⚠️:Page是和线程相关的,如果所在线程不正确,会报错。
其主要定义如下:
class AutoreleasePoolPage
{
// 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
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
PAGE_MAX_SIZE; // must be multiple of vm page size
#else
PAGE_MAX_SIZE; // size and alignment, power of 2
#endif
static size_t const COUNT = SIZE / sizeof(id);
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
}
=======================PUSH开始=======================
- push下的autoreleaseFast函数
static inline void *push()
{
id *dest;
if (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
函数:
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);
}
}
hotPage个人理解就是当前正在使用的Page,该Page可能还是空,没有创建,和标记EMPTY_POOL_PLACEHOLDER
做比较,然后返回空;如果创建了,则需要检查一下是否符合一些要求(这之后唯一能看懂的就是线程要能对应,如果不正确,会出错):
static inline AutoreleasePoolPage *hotPage()
{
AutoreleasePoolPage *result = (AutoreleasePoolPage *)
tls_get_direct(key); // 根据key获取到Page
if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil; // 检测是否还是空的,没有用过,如果是,则直接返回nil
if (result) result->fastcheck(); // 检测是否否和一些要求,这里对当前线程也做了检测,其他的暂时没看懂
return result;
}
继续说回上面的函数autoreleaseFast
。在这里做了如下判断:
1、当前的Page是否存在并且没有满(即空间没有使用完),如果是,则做两件事情:
a、执行添加,并且返回当前next所在的位置给__AtAutoreleasePool
的变量atautoreleasepoolobj
,该位置就是当前加入pool中变量的地址,方便后期释放使用。
b、在add
方法中修改next指针位置,并把该位置上的值设置为空POOL_BOUNDARY
。add方法定义如下:
id *add(id obj) // 主要做的事情是把next移动一位,并把它的值设置为nil,然后返回
{
assert(!full());
unprotect();
// 用ret保存当前next的位置,该位置要返回给相应的pool中的变量
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj; // 修改next指针位置,使之后移一位,并且清空当前位置上的值,++的优先级要高于*的优先级
protect();
return ret;
}
2、如果存在但是当前page已经满了,调用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);
// 使用遍历,找到最后一个满了的page,然后创建一个Page
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page); // 在这里完成了双向链表的链接
} while (page->full());
setHotPage(page); // 设置当前page为hotPage
return page->add(obj); // 添加page,并且返回添加obj之前next的位置
}
在该方法中做了如下的事情:
a、通过循环遍历,找到最后一个page,并且调AutoreleasePoolPage
初始化,完成page的链接
b、设置新page为hotPage,即当前page
c、执行添加操作
3、如果没有Page,则调用autoreleaseNoPage
添加
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",
pthread_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); // 因为当前自动释放池中还没有page,所以这里创建的就是第一个根page
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);
}
==========================PUSH完成==========================
==========================POP开始===========================
当前的pool释放的时候,会调用其析构函数进行释放,在析构函数里只调用了objc_autoreleasePoolPop函数,该函数最后调用的也是AutoreleasePoolPage下的pop函数。
- pop
static inline void pop(void *token) // 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()); // codePage其实就是得到根Page,通过begin找到该page下第一个对象的位置
} else {
// Pool was never used. Clear the placeholder.
setHotPage(nil);
}
return;
}
page = pageForPointer(token); // 找到当前对象token所在的page
stop = (id *)token; // 确定停止位置,停止位置为释放的变量的地址
if (*stop != POOL_BOUNDARY) { // 该对象存在
if (stop == page->begin() && !page->parent) { // 如果是个空的page,且不是根page
// 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); // 释放当前page上的变量
// memory: delete empty children 释放空的page
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();
}
}
}
在这里依次干了这么几件事情:
- 判断当前的空的要释放的对象是否是空标记EMPTY_POOL_PLACEHOLDER,表示当前的Page是空的,如果是,则调用方法
coldPage
获取到根Page以及其内容的开始位置begin(因为每个对象内部都有自己的方法和变量,然后才是开始存储需要自动释放的变量),另外这里是迭代调用:
static inline AutoreleasePoolPage *coldPage()
{
AutoreleasePoolPage *result = hotPage();
if (result) {
while (result->parent) {
result = result->parent;
result->fastcheck();
}
}
return result;
}
- 通过方法
pageForPointer
获取到当前需要释放的变量所在的page的其实位置,即this的位置
static AutoreleasePoolPage *pageForPointer(const void *p)
{
return pageForPointer((uintptr_t)p);
}
static AutoreleasePoolPage *pageForPointer(uintptr_t p)
{
AutoreleasePoolPage *result;
uintptr_t offset = p % SIZE; // 通过取余操作,获取到当前对象在page中的偏移量
assert(offset >= sizeof(AutoreleasePoolPage));
result = (AutoreleasePoolPage *)(p - offset); // 当前对象的地址减去偏移量,就是当前page的起始位置
result->fastcheck();
return result;
}
- 确定停止位置,停止位置其实就是当前需要释放的变量的地址
- 如果停止位置为nil,即
POOL_BOUNDARY
,则需要确定其满足条件:hotPage的管理外部释放变量的起始位置begin
,并且是根Page,否则会出错:
if (*stop != POOL_BOUNDARY) { // 该对象存在
if (stop == page->begin() && !page->parent) { // 如果是个空的page,且不是根page
// 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);
}
}
- 开始释放到该位置的所有的变量(即存储位置比该位置高,或者在该位置之后的其他的page里的变量)
void releaseUntil(id *stop)
{
// Not recursive: we don't 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 can't prove it
while (page->empty()) { // 找到那个有自动释放对象的page
page = page->parent;
setHotPage(page);
}
page->unprotect();
id obj = *--page->next; // id obj = *--(page->next) 通过将page的next指针下移一个来获取到需要释放的对象
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(内容为空:begin == next)
这里有一个特殊处理,如果当前page的内容少于一半的存储空间,则直接清除其空的child,否则清除其child的child。这样做可能是为了减少后续的分配空间的时间支出吧。
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();
}
}
==========================POP完成===========================
总结
push的过程为:调用autoreleaseFast方法完成创建,在该方法中做了三个判断:
- hotPage是否存在且没有满
如果没有没满,则直接添加 - hotPage是否存在
满了,则需要调用autoreleaseFullPage,在里面进行创建新的page并链接起来 - hotPage不存在
调用autoreleaseNoPage,创建一个根Page
pop的过程则为:调用pop方法完成:
- 检查当前的Page是否为空,如果为空,则找到coldPage和其begin位置
- 找到当前释放对象所在Page
- 确定停止释放的位置
- 释放比停止位置地址高的变量和page
- 释放空的page,只不过对于当前page的存储是否过半做了区别处理
网友评论