ObjC runtime原始碼 閱讀筆記(一)
我的部落格Max`s Blog
本文的原始碼來自於apple opensource。
1.objc-private.h
開啟標頭檔案就看到了兩個熟悉的結構體指標
typedef struct objc_class *Class;
typedef struct objc_object *id;
我們會經常用到id這個指標,比較老的Foundation框架中,一般的初始化方法都會返回一個id物件,並且一些有iOS程式設計經驗的老鳥也會說ObjC中的所有物件都可以強轉成id型別。那麼現在就來分析一下id究竟是個什麼東東。
從原始碼來看真的是好長的一坨結構體,首先看到的是一個isa_t的union:
private:
isa_t isa;
這個isa_t用一句話概括就是:
對64位的裝置物件進行類物件指標的優化,利用合理的bit(arm64裝置為32位)儲存類物件的地址,其他位用來進行記憶體管理。這種優化模式被稱為tagged pointer。用在isa_t的實現中稱作IndexedIsa。
下面是isa_t在arm64架構下的結構:
union isa_t
{
uintptr_t bits;
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
struct {
uintptr_t indexed : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
};
-
indexed:標記是否啟動指標優化
-
has_assoc:是否有關聯物件
-
has_cxx_dtor:是否有析構器
-
shiftcls:類物件指標
-
magic:標記初始化完成
-
weakly_refrenced:是否弱引用
-
deallocating:是否正在釋放
-
extra_rc:引用計數(但是比retaincount小1)
至此,優化情況下的isa_t包含的內容大體總結完畢。
回過頭來繼續分析objc_object內的函式。
Class ISA() //不支援tagged pointer時候獲取Class的函式
Class getIsa() //支援tagged pointer時候獲取Class的函式
下面是一系列isa初始化的函式:
void initIsa(Class cls /*indexed=false*/);
void initClassIsa(Class cls /*indexed=maybe*/);
void initProtocolIsa(Class cls /*indexed=maybe*/);
void initInstanceIsa(Class cls, bool hasCxxDtor);
值得注意的是這幾個函式最後呼叫的都是
inline bool objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor)
下面的函式是用來改變一個物件的Class:
Class changeIsa(Class newCls);
印象中KVO的實現中,會改變一個物件的Class,以後會帶來驗證。
接下來的一系列函式顧名思義,我也不做解釋,內部的實現基本也都是依賴於isa來進行的。
// changeIsa() should be used to change the isa of existing objects.
// If this is a new object, use initIsa() for performance.
Class changeIsa(Class newCls);
bool hasIndexedIsa();
bool isTaggedPointer();
bool isClass();
// object may have associated objects?
bool hasAssociatedObjects();
void setHasAssociatedObjects();
// object may be weakly referenced?
bool isWeaklyReferenced();
void setWeaklyReferenced_nolock();
// object may have -.cxx_destruct implementation?
bool hasCxxDtor();
這裡我解釋一下下面這兩個方法的區別。
bool hasIndexedIsa();
bool isTaggedPointer();
本質上這兩個函式都是來判斷某個指標是否啟用了tagged pointer。不同的是
bool hasIndexedIsa();是用來判斷當前物件的isa是否啟用tagged pointer,而bool isTaggedPointer();函式用來判斷當前的物件指標是否啟用了tagged pointer。根據我的調研,比如NSNumber、NSDate等值佔用記憶體比較少的物件啟用了tagged pointer。
接下來是一系列管理引用計數以及生命週期的函式:
// Optimized calls to retain/release methods
id retain();
void release();
id autorelease();
// Implementations of retain/release methods
id rootRetain();
bool rootRelease();
id rootAutorelease();
bool rootTryRetain();
bool rootReleaseShouldDealloc();
uintptr_t rootRetainCount();
// Implementation of dealloc methods
bool rootIsDeallocating();
void clearDeallocating();
void rootDealloc();
是不是很熟悉呢?先看一下id retain()的內部實現:
inline id
objc_object::retain()
{
// UseGC is allowed here, but requires hasCustomRR.
assert(!UseGC || ISA()->hasCustomRR());
assert(!isTaggedPointer());
if (! ISA()->hasCustomRR()) {
return rootRetain();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
翻譯一下,如果使用GC但是並沒有custom的retain/release方法則會直接斷言掉,如果支援tagged pointer就會直接斷言掉。接下來的流程就是如果沒有custom的retain/release方法就會呼叫rootRetain()。兜兜轉轉最後會呼叫如下方法:
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
assert(!UseGC);
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (!newisa.indexed) goto unindexed;
// don`t check newisa.fast_rr; we already called any RR overrides
if (tryRetain && newisa.deallocating) goto tryfail;
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
if (carry) {
// newisa.extra_rc++ overflowed
if (!handleOverflow) return rootRetain_overflow(tryRetain);
// Leave half of the retain counts inline and
// prepare to copy the other half to the side table.
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
} while (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits));
if (transcribeToSideTable) {
// Copy the other half of the retain counts to the side table.
sidetable_addExtraRC_nolock(RC_HALF);
}
if (!tryRetain && sideTableLocked) sidetable_unlock();
return (id)this;
tryfail:
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
unindexed:
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
這段程式碼確實值得仔細研讀,轉換成虛擬碼的形式為:
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (not support tagged pointer) return this;
do {
if (isa not support indexed)
sidetable_tryRetain(); //利用sidetable進行管理物件的引用計數。
if (isa support indexed)
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc+
}
}
當然內部還有一些對於retry以及overflow的處理,這篇文章就不做過多的分析。但是大體上對於沒有進行isa的indexed優化的物件的引用計數是依賴於SideTable()管理,而進行indexed優化的物件則直接利用isa指標進行管理。
接下來的private函式我就不多做分析了,值得注意的是,裡面有很多有關於sidetable的函式,但是這些函式是在NSObject.mm中實現的。
這樣有關於objc_obejct這個結構體的分析就到此為止。接下來還有一系列的文章進行分析。