群组
Flutter,iOS,Android,Python等,闲聊工作&技术&问题;
个人主页:https://waitwalker.cn
telegram群链接:https://t.me/joinchat/Ej0o0A1ntlq5ZFIMzzO5Pw
Autorelease翻译过来就是自动释放,什么是自动释放,怎么自动释放,什么时候自动释放.这些是本文的研究重点.自动释放首先要从autoreleasepool说起.
autoreleasepool是什么?
通过clang编译器重写以下我们的Objective-C代码:
// autoreleasepool
@autoreleasepool {
}
这里我们定义了一个autoreleasepool对象,查看一下clang编译器重写后的源码:
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
// __AtAutoreleasePool声明结构
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
可以看到通过AtAutoreleasePool结构构造了一个 autoreleasepool实例,AtAutoreleasePool()方法被调用即执行atautoreleasepoolobj = objc_autoreleasePoolPush()方法;析构时,即超出AtAutoreleasePool的作用域,调用~__AtAutoreleasePool()方法,即即执行的是objc_autoreleasePoolPop(atautoreleasepoolobj)方法.其过程可以细分为以下过程(以objc-loadmethod.mm中调用load方法为例):
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
// push
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
// pop
objc_autoreleasePoolPop(pool);
loading = NO;
}
因此我们可以初步总结autoreleasepool的执行过程主要有:
1)objc_autoreleasePoolPush()构造;
2)autorelease操作;
3)objc_autoreleasePoolPop(void ctxt)析构
而objc_autoreleasePoolPush()和objc_autoreleasePoolPop(void ctxt)内部分别调用的是:
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
可以看到分别调用的是AutoreleasePoolPage中的成员方法;AutoreleasePoolPage,在NSObject.mm文件中:
// MARK: - AutoReleasepool class
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;
// next指针指向最新添加的autoreleased对象的下一个位置,初始化时指向begin()
id *next;
// 指向当前线程
pthread_t const thread;
// 父节点
AutoreleasePoolPage * const parent;
// 子节点
AutoreleasePoolPage *child;
// 节点深度
uint32_t const depth;
uint32_t hiwat;
}
查看autoreleasepool的源码可以看出,autoreleasepool没有单独的内存结构,它是以 AutoreleasePoolPage为单个节点组成的双向链表来实现的.autoreleasePoolPage中一些成员变量:
magic:校验autoreleasepoolpage结构完整性;
next:指向最新添加的autoreleased对象的下一个位置;
thread:指向当前线程;
parent:指向父节点,第一个节点parent为nil(相当于表头);
child:指向子节点,最后一个节点child为nil(相当于表尾);
push()方法
push()方法也就是创建一个autoreleasepoolpage实例的过程:
// push
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) {
// 每一个autoreleasepool对象开始于一个新的autoreleasepoolpage
// 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;
}
其逻辑过程:
// 创建一个page逻辑
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最多存储有4096个bytes:
#define I386_PGBYTES 4096 /* bytes per 80386 page */
#define I386_PGSHIFT 12 /* bitshift for pages */
#define PAGE_SIZE I386_PGBYTES
当第一次创建的时候传入的是nil,所以调用autoreleaseNoPage()函数,创建一个新的page:
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()) {
// 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);
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()方法类似于autoreleasepool的初始化方法,先创建一个autoreleasepoolpage节点,然后想page中添加一个nil对象占位.
dest = autoreleaseFast(POOL_BOUNDARY);
define POOL_BOUNDARY nil
autorelease()方法
查看一下NSObject.h提供的autorelease方法接口,其调用栈:
- (instancetype)autorelease OBJC_ARC_UNAVAILABLE;
// Replaced by ObjectAlloc
- (id)autorelease {
return ((id)self)->rootAutorelease();
}
// MARK: - autorelease 实现
// Base autorelease implementation, ignoring overrides.
inline id
objc_object::rootAutorelease()
{
if (isTaggedPointer()) return (id)this;
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
return rootAutorelease2();
}
__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{
assert(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
可以看到autorelease最终调用的是AutoreleasePoolPage中的成员方法autorelease();
// 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;
}
AutoreleasePoolPage中的成员方法autorelease()内部调用的是:
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);
}
}
其逻辑是:
1)先判断当前page存在并且没满,则将released obj 添加到当前page;
2)如果当前page已满,则创建一个新的page,并将当前page的child指针指向new page;把released obj 添加到new page中;
// MARK: - 创建一个new page
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);
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
}
3)如果当前page不存在,则直接创建一个new page,并把released obj添加到new page中;没有page意味着autoreleasepool没有进行push操作或者autoreleasepool已经push操作了,但是没有添加任何released的obj或者添加的是nil对象,则创建一个空的autoreleasepool进行占位;
// autoreleasepool中没有page
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()) {
// 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) {//如果没有创建一个autoreleasepool,则直接抛异常
// 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) {//如果对象为空,并非debug环境,创建一个空的autore占位
// 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);
}
// 将released obj添加到page中
// Push the requested object or pool.
return page->add(obj);
}
以上三个过程都有执行add()操作,具体看一下add():
// 向page中添加released操作
id *add(id obj)
{
assert(!full());
unprotect();
// 将next节点赋值给ret
id *ret = next; // faster than `return next-1` because of aliasing
// next指针增大一个位置
*next++ = obj;
protect();
return ret;
}
向一个对象发送一条 -autorelease消息,其实就是将这个对象加入到AutoreleasePoolPage中next指针指向的位置,然后next增大一个位置.
pop()方法
pop操作上面已经提到了是autoreleasepool的析构函数也就是page的销毁函数.
// pop
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
// 如果当前obj是nil
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;
}
// 根据token获取page
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();
}
}
}
pop()函数有个参数是push()操作的返回值即next指针指向内存地址.然后拿到这个地址遍历链表遍历当前page,然后向page中的obj发送release消息,直到next指向nil:
// MARK: - 释放obj
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
// 获取page的父指针
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
// 根据next指针缩小一个位置获取obj
id obj = *--page->next;
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
if (obj != POOL_BOUNDARY) {
// 发送release 消息
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
}
总结
AutoreleasePoolPage是双链表的一个节点,其内部有可以理解为一个存储released obj地址的堆栈容器,每一个新加入的released obj被添加到这个容器里面,然后有一个next指针指向最后被加入的obj地址的下一个位置.如果当前page SIZE 满了,创建一个新的page. pop()操作就是获取到page中的每一个obj,给他们发送release()消息.
编译后源码库
编译后的源码放在Github, 如果对你有帮助,请给一个star吧!
博客地址&相关文章
博客地址: https://waitwalker.cn/
系列文章:
5. Ivar objc_property_t Protocol解读
参考文献
《Objective-C高级编程:iOS与OS X多线程和内存管理》(Pro multithreading and memory management for iOS and OS X)–译者: 黎 华