RunTime源码阅读(八)之autoreleasepool
2020-01-26 本文已影响0人
某非著名程序员
1.autoreleasepool特性
- 在ARC时只要保证在@autoreleasepool{}包裹,变量超出函数的区域,不会被释放,直到@autoreleasepool结束被释放。
- 对于系统方法,系统会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleasepool。(结论来自于Objective-C高级编程+iOS与OS+X多线程和内存管理)。这个可以在objc-runtime源码中验证。如NSString中的stringWithFormat:,NSArray中的arrayWithObject。
2. 原理
autoreleasepool:以栈为节点的双链表结构。
@autoreleasepool{}相当于调用了objc_autoreleasePoolPush、objc_autoreleasePoolPop
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
AutoreleasePoolPage数据结构
# 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;
AutoreleasePoolPage是以链表的形式存储,parent与child组成双链表结构。
*next是真正存储obj的地方,是一个静态数组,控制一头进行添加与删除,形成栈的结构,SIZE是分配的大小。
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;
}
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {//page存在且没有满
return page->add(obj);//添加obj
} else if (page) {//page存在且满了,需要分配新的页
return autoreleaseFullPage(obj, page);
} else {//page为空时
return autoreleaseNoPage(obj);
}
}
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;
if (haveEmptyPoolPlaceholder()) {//已经有空的pool占位
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
objc_autoreleaseNoPool(obj);
return nil;
}
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
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);//添加对象
}
objc_autoreleasePoolPush:初始化时会为setEmptyPoolPlaceholder分配一个占位。
当有嵌套时,pushExtraBoundary设置为YES,添加POOL_BOUNDARY哨兵对象,再添加obj对象。
stringWithFormat与arrayWithObject发生什么
2.1 stringWithFormat
stringWithFormat__attribute__((aligned(16)))
id
objc_autorelease(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->autorelease();
}
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);
assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {//page存在且没有满
return page->add(obj);//添加obj
} else if (page) {//page存在且满了,需要分配新的页
return autoreleaseFullPage(obj, page);
} else {//page为空时
return autoreleaseNoPage(obj);
}
}
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
stringWithFormat会调用objc_autorelease-调用NSObject中的autorelease-调用autoreleaseFast-调用page->add(obj);
2.2 arrayWithObject
arrayWithObject与stringWithFormat一样调用了objc_autorelease-调用page->add(obj);
2.3 objc_autoreleasePoolPop
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();
}
}
}
static inline AutoreleasePoolPage *coldPage()
{
AutoreleasePoolPage *result = hotPage();
if (result) {
while (result->parent) {
result = result->parent;
result->fastcheck();
}
}
return result;
}
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为空时,查找parent为hotPage
page = page->parent;
setHotPage(page);
}
page->unprotect();
id obj = *--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
}
在@autoreleasePool结束时调用objc_autoreleasePoolPop-调用releaseUntil-出栈,调用objc_release释放对象
结论:
- @autoreleasepool遵循引用计数原理
- 系统的非alloc/new/copy/mutableCopy方法会调用autorelease方法,最终会调用page-add(obj)添加到队列中;释放时会调用objc_release。
- ARC多数情况下并不需要手动调用@autoreleasepool,但在访问大内存块时,如图片,可以用@autoreleasepool包裹,能做到及时释放。