书接上文,接下来让我们了解一下,苹果是如何实现 autorelease 和 autoreleasepool 的
autorelease
autorelease 方法的作用是延迟对象的 release,通常用于返回值时使用,如下是它的实现:
// Replaced by ObjectAlloc
- (id)autorelease {
return ((id)self)->rootAutorelease();
}
// Base autorelease implementation, ignoring overrides.
inline id
objc_object::rootAutorelease()
{
if (isTaggedPointer()) return (id)this; // 如果是 tagged pointer,直接返回 this
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this; // 如果 prepareOptimizedReturn(ReturnAtPlus1) 返回 true,直接返回 this
return rootAutorelease2(); // 调用 rootAutorelease2
}
复制代码
prepareOptimizedReturn
prepareOptimizedReturn 是用于优化返回的,其实现如下
// Try to prepare for optimized return with the given disposition (+0 or +1).
// Returns true if the optimized path is successful.
// Otherwise the return value must be retained and/or autoreleased as usual.
static ALWAYS_INLINE bool
prepareOptimizedReturn(ReturnDisposition disposition)
{
assert(getReturnDisposition() == ReturnAtPlus0); // 确保当前获取的 Disposition 为 false
if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) { // 如果当前允许优化返回值
if (disposition) setReturnDisposition(disposition); // 设置 ReturnDisposition 为 true
return true;
}
return false;
}
复制代码
ReturnDisposition & setReturnDisposition
ReturnDisposition & setReturnDisposition 是用于获取和设置 Disposition 用的函数,实现如下
static ALWAYS_INLINE ReturnDisposition
getReturnDisposition()
{
return (ReturnDisposition)(uintptr_t)tls_get_direct(RETURN_DISPOSITION_KEY);
}
static ALWAYS_INLINE void
setReturnDisposition(ReturnDisposition disposition)
{
tls_set_direct(RETURN_DISPOSITION_KEY, (void*)(uintptr_t)disposition);
}
复制代码
tls,全程为 Thread Local Storage,是在当前线程存储一些数据用的,在这里是通过 RETURN_DISPOSITION_KEY 这个 key 进行存储的,其实现如下:
typedef pthread_key_t tls_key_t;
#if defined(__PTK_FRAMEWORK_OBJC_KEY0)
# define SUPPORT_DIRECT_THREAD_KEYS 1
# define TLS_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY0)
# define SYNC_DATA_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY1)
# define SYNC_COUNT_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY2)
# define AUTORELEASE_POOL_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY3)
# if SUPPORT_RETURN_AUTORELEASE
# define RETURN_DISPOSITION_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY4)
# endif
#else
# define SUPPORT_DIRECT_THREAD_KEYS 0
#endif
复制代码
__builtin_return_address
gcc 的编译特性使用 __builtin_return_address(level) 打印出一个函数的堆栈地址。其中 level 代表是堆栈中第几层调用地址,__builtin_return_address(0) 表示第一层调用地址,即当前函数,__builtin_return_address(1) 表示第二层。
callerAcceptsOptimizedReturn
callerAcceptsOptimizedReturn 是用来判断当前返回是否可以优化的函数,其在不同架构上的实现不同,我暂时没搞懂这段代码到底是什么意思,等我哪天想清楚了再补上这个坑吧,先贴上其在 arm 架构下的实现:
static ALWAYS_INLINE bool
callerAcceptsOptimizedReturn(const void *ra)
{
// if the low bit is set, we're returning to thumb mode
if ((uintptr_t)ra & 1) {
// 3f 46 mov r7, r7
// we mask off the low bit via subtraction
// 16-bit instructions are well-aligned
if (*(uint16_t *)((uint8_t *)ra - 1) == 0x463f) {
return true;
}
} else {
// 07 70 a0 e1 mov r7, r7
// 32-bit instructions may be only 16-bit aligned
if (*(unaligned_uint32_t *)ra == 0xe1a07007) {
return true;
}
}
return false;
}
复制代码
rootAutorelease2
如果之前的一系列优化没能返回 this,最后会调用 rootAutorelease2 这个方法,其实现如下:
__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{
assert(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
复制代码
我们会发现其实现相当简单,接下来就是 autorelease 的重头戏,autoreleasepool 究竟是怎么实现的。
autoreleasepool
想要彻底了解 autoreleasepool 的实现,不是三言两语可以说清楚的,让我们接着之前的 AutoreleasePoolPage::autorelease((id)this); 来一步步分析 autoreleasepool 是如何实现的:
首先需要了解的一点是,所有 autorelease 相关的方法,最终都是通过 AutoreleasePoolPage 这个类来实现的,其 autorelease 方法实现如下:
static inline id autorelease(id obj)
{
assert(obj); // 对象不可为 NULL
assert(!obj->isTaggedPointer()); // 对象不可为 tagged pointer
id *dest __unused = autoreleaseFast(obj);
assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj); // 确保 dest 不存在或等于 EMPTY_POOL_PLACEHOLDER 或等于 obj
return obj;
}
复制代码
autoreleaseFast
autoreleaseFast 的实现如下:
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage(); // 获取 hotPage
if (page && !page->full()) { // 如果 page 存在且未满
return page->add(obj); // 将对象添加到 page 中
} else if (page) { // 如果 page 存在,但已经满了。
return autoreleaseFullPage(obj, page); // 调用 autoreleaseFullPage 方法
} else { // 如果 page 为 NULL
return autoreleaseNoPage(obj); // 调用 autoreleaseNoPage 方法
}
}
复制代码
到这里我们可以大致了解到,所谓的 autoreleasepool 其实是由一个或多个 AutoreleasePoolPage 对象组成的,这些对象有容量限制,让我们继续拆解这个方法。
hotPage
hotPage 方法是用来获取当前可用 page 的,其实现如下:
static inline AutoreleasePoolPage *hotPage()
{
AutoreleasePoolPage *result = (AutoreleasePoolPage *)
tls_get_direct(key); // 通过 tls 查询可用 AutoreleasePoolPage 对象
if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil; // 如果查询结果为 EMPTY_POOL_PLACEHOLDER,返回 nil
if (result) result->fastcheck(); // fastcheck
return result;
}
复制代码
这里的 key 定义如下:
// Thread keys reserved by libc for our use.
#if defined(__PTK_FRAMEWORK_OBJC_KEY0)
# define SUPPORT_DIRECT_THREAD_KEYS 1
# define TLS_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY0)
# define SYNC_DATA_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY1)
# define SYNC_COUNT_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY2)
# define AUTORELEASE_POOL_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY3)
# if SUPPORT_RETURN_AUTORELEASE
# define RETURN_DISPOSITION_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY4)
# endif
#else
# define SUPPORT_DIRECT_THREAD_KEYS 0
#endif
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
复制代码
fastcheck 方法实现如下:
void check(bool die = true)
{
if (!magic.check() || !pthread_equal(thread, pthread_self())) {
busted(die);
}
}
void fastcheck(bool die = true)
{
#if CHECK_AUTORELEASEPOOL
check(die);
#else
if (! magic.fastcheck()) {
busted(die);
}
#endif
}
复制代码
一般会做一些线程之类的检查,最终都会调用到 busted 方法,也就是实际做检查的地方,
void busted(bool die = true)
{
magic_t right;
(die ? _objc_fatal : _objc_inform)
("autorelease pool page %p corrupted\n"
" magic 0x%08x 0x%08x 0x%08x 0x%08x\n"
" should be 0x%08x 0x%08x 0x%08x 0x%08x\n"
" pthread %p\n"
" should be %p\n",
this,
magic.m[0], magic.m[1], magic.m[2], magic.m[3],
right.m[0], right.m[1], right.m[2], right.m[3],
this->thread, pthread_self());
}
复制代码
根据 die 参数不同会决定是 _objc_fatal 或 _objc_inform。
full
full 方法是用来检查 AutoreleasePoolPage 对象是否已经满了的方法,其实现如下:
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
bool full() {
return next == end();
}
复制代码
看到此处,我们已经可以断定 AutoreleasePoolPage 对象的数据类型是链表类型了。
SIZE 的定义如下:
#define I386_PGBYTES 4096
#define PAGE_SIZE I386_PGBYTES
#define PAGE_MAX_SIZE PAGE_SIZE
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
复制代码
add
id *add(id obj)
{
assert(!full()); // 确保当前状态不满
unprotect(); // 设置为读/写状态
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect(); // 设置为只读状态
return ret;
}
复制代码
protect 和 unprotect 方法实现如下:
inline void protect() {
#if PROTECT_AUTORELEASEPOOL
mprotect(this, SIZE, PROT_READ);
check();
#endif
}
inline void unprotect() {
#if PROTECT_AUTORELEASEPOOL
check();
mprotect(this, SIZE, PROT_READ | PROT_WRITE);
#endif
}
复制代码
主要是使用了 mprotect 函数,mprotect 函数的原型如下:
int mprotect(const void *addr, size_t len, int prot);
复制代码
其中 addr 是待保护的内存首地址,必须按页对齐;len 是待保护内存的大小,必须是页的整数倍,prot 代表模式,可能的取值有 PROT_READ(表示可读)、PROT_WRITE(可写)等。
不同体系结构和操作系统,一页的大小不尽相同。如何获得页大小呢?通过 PAGE_SIZE 宏或者 getpagesize() 系统调用即可。
autoreleaseFullPage
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; // 如果 page->child 存在,则给 page 赋值 page->child
else page = new AutoreleasePoolPage(page); // 否则就初始化一个新的 page。
} while (page->full());
setHotPage(page); // 设置 page 为当前 hotPage
return page->add(obj); // 添加 obj 进入 page
}
复制代码
AutoreleasePoolPage 的构造函数如下所示:
AutoreleasePoolPage(AutoreleasePoolPage *newParent)
: magic(), next(begin()), thread(pthread_self()),
parent(newParent), child(nil),
depth(parent ? 1+parent->depth : 0),
hiwat(parent ? parent->hiwat : 0)
{
if (parent) {
parent->check();
assert(!parent->child);
parent->unprotect();
parent->child = this;
parent->protect();
}
protect();
}
复制代码
通过初始化方法我们可以看到,AutoreleasePoolPage 对象不仅有 child,还有 parent,由此可以断定,其为双向链表。
setHotPage 实现如下:
static inline void setHotPage(AutoreleasePoolPage *page)
{
if (page) page->fastcheck();
tls_set_direct(key, (void *)page);
}
复制代码
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;
if (haveEmptyPoolPlaceholder()) { // 如果当前有 poolPlaceholder
// 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);
}
复制代码
haveEmptyPoolPlaceholder 方法实现如下:
# define EMPTY_POOL_PLACEHOLDER ((id*)1)
static inline bool haveEmptyPoolPlaceholder()
{
id *tls = (id *)tls_get_direct(key);
return (tls == EMPTY_POOL_PLACEHOLDER);
}
复制代码
setEmptyPoolPlaceholder 方法实现如下:
static inline id* setEmptyPoolPlaceholder()
{
assert(tls_get_direct(key) == nil);
tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER);
return EMPTY_POOL_PLACEHOLDER;
}
复制代码