書接上文,接下來讓咱們瞭解一下,蘋果是如何實現 autorelease 和 autoreleasepool 的架構
autorelease 方法的做用是延遲對象的 release,一般用於返回值時使用,以下是它的實現:ide
// 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 是用於優化返回的,其實現以下函數
// 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 是用於獲取和設置 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 進行存儲的,其實現以下:ui
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
複製代碼
gcc 的編譯特性使用 __builtin_return_address(level) 打印出一個函數的堆棧地址。其中 level 表明是堆棧中第幾層調用地址,__builtin_return_address(0) 表示第一層調用地址,即當前函數,__builtin_return_address(1) 表示第二層。this
callerAcceptsOptimizedReturn 是用來判斷當前返回是否能夠優化的函數,其在不一樣架構上的實現不一樣,我暫時沒搞懂這段代碼究竟是什麼意思,等我哪天想清楚了再補上這個坑吧,先貼上其在 arm 架構下的實現:spa
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;
}
複製代碼
若是以前的一系列優化沒能返回 this,最後會調用 rootAutorelease2 這個方法,其實現以下:操作系統
__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{
assert(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
複製代碼
咱們會發現其實現至關簡單,接下來就是 autorelease 的重頭戲,autoreleasepool 到底是怎麼實現的。線程
想要完全瞭解 autoreleasepool 的實現,不是三言兩語能夠說清楚的,讓咱們接着以前的 AutoreleasePoolPage::autorelease((id)this); 來一步步分析 autoreleasepool 是如何實現的:debug
首先須要瞭解的一點是,全部 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 的實現以下:
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 方法是用來獲取當前可用 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 方法是用來檢查 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
複製代碼
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() 系統調用便可。
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);
}
複製代碼
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;
}
複製代碼