以前只是看了很多部落格,這次打算看一下原始碼,並記錄下來。想到哪裡就讀到哪裡,寫到哪裡。讀的程式碼版本是:
objc runtime 680
,可以從這裡下載
物件與 isa 指標
開始閱讀原始碼,首先 開啟 objc-private.h
檔案,檢視對於 Objectiv-C 的物件的定義
1 2 3 4 5 6 7 8 |
struct objc_object { private: isa_t isa; public: void initIsa(Class cls /*indexed=false*/); private: void initIsa(Class newCls, bool indexed, bool hasCxxDtor); } |
每個物件都包含一個 isa指標,指向 isa_t 結構體,對isa_t結構體的內部一探究竟
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits; #if SUPPORT_NONPOINTER_ISA # if __arm64__ # define ISA_MASK 0x0000000ffffffff8ULL # define ISA_MAGIC_MASK 0x000003f000000001ULL # define ISA_MAGIC_VALUE 0x000001a000000001ULL 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) }; # elif __x86_64__ # 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 : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000 uintptr_t magic : 6; uintptr_t weakly_referenced : 1; uintptr_t deallocating : 1; uintptr_t has_sidetable_rc : 1; uintptr_t extra_rc : 8; # define RC_ONE (1ULL<<56) # define RC_HALF (1ULL<<7) }; } |
可以看到原始碼裡面有一個#if SUPPORT_NONPOINTER_ISA
來判斷是否支援isa指標優化,那麼來看一下SUPPORT_NONPOINTER_ISA
的具體實現,開啟objc-config.h
的82行可以看到
1 2 3 4 5 6 |
// Define SUPPORT_NONPOINTER_ISA=1 to enable extra data in the isa field. #if !__LP64__ || TARGET_OS_WIN32 || TARGET_IPHONE_SIMULATOR # define SUPPORT_NONPOINTER_ISA 0 #else # define SUPPORT_NONPOINTER_ISA 1 #endif |
我們的電腦是 x86_64的處理器,那麼TARGET_IPHONE_SIMULATOR也是x86_64的處理器,那麼可以得知,目前只有arm64裝置支援isa優化,我們所使用的手機正是支援此優化
目前我們使用的裝置都是 64位的,也就是說isa 指標是一個64 bit的指標,那麼如果全用來存放記憶體地址就顯得有些浪費,於是蘋果有引入一種技術叫 Tagged Pointer
。
64位超大地址的出現,如果僅用來存放記憶體地址比較浪費,我們可以在指標地址中儲存或附加更多的資訊,這就是Tagged Pointer
那麼tagged pointer
在 isa中有什麼運用呢?可以看出來 isa_t結構體中 64位並不是全部用來存放記憶體地址,到底怎麼放,來看一下 isa指標的初始化過程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
inline void objc_object::initIsa(Class cls) { initIsa(cls, false, false); } inline void objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) { assert(!isTaggedPointer()); if (!indexed) { isa.cls = cls; } else { assert(!DisableIndexedIsa); isa.bits = ISA_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.indexed is part of ISA_MAGIC_VALUE isa.has_cxx_dtor = hasCxxDtor; isa.shiftcls = (uintptr_t)cls >> 3; } } |
會根據傳入的indexed來判斷進行那種初始化方式,如果是indexed為0,則仍然按照以前的方式進行初始化,也就是訪問isa指標的時候,直接返回指向class的指標。也不會利用到剛才所講到Tagged Pointer
1 2 3 4 5 6 7 8 |
union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits; } |
當indexed為1的時候,就會啟動優化isa指標優化,也就是說isa不再單單是類的指標,還包含更多的資訊,比如:引用計數、是否被weak引用等情況。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# if __arm64__ # define ISA_MASK 0x0000000ffffffff8ULL # define ISA_MAGIC_MASK 0x000003f000000001ULL # define ISA_MAGIC_VALUE 0x000001a000000001ULL 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) }; |
既然已經揭開了 結構體的面紗,就接著分析下每個變數所對應的含義吧
- has_assoc
- 表示該物件是否包含 關聯物件
- has_cxx_dtor
- 表示 該物件是否有 C++ 或者 Objc 的析構器
- shiftcls
- 類的指標
- magic
- 判斷物件是否初始化完成
- weakly_referenced
- 物件是否被指向一個弱變數
- deallocating
- 物件正在釋放記憶體
- has_sidetable_rc
- 判斷該物件的引用計數是否過大,如果過大則需要其他雜湊表來進行儲存
- extra_rc
- 存放該物件的引用計數值減一後的結果
引用計數
剛才說到 isa裡面儲存引用計數的問題,如果不支援isa優化,或者說,isa裡面儲存不夠用,這個時候就需要把引用計數交給SideTable去管理
1 2 3 4 5 |
struct SideTable { spinlock_t slock; //鎖 RefcountMap refcnts; //儲存引用計數的的雜湊表 weak_table_t weak_table; //儲存weak引用的雜湊表 } |
對於引用計數計數的雜湊表定義如下
1 2 3 |
// RefcountMap disguises its pointers because we // don't want the table to act as a root for `leaks`. typedef objc::DenseMap,size_t,true> RefcountMap; |
DenseMap是用來儲存引用計數,Key可以理解為物件的記憶體地址,value對應的是引用計數的值減 1
weak 表示弱引用,這個引用不會增加物件的引用計數,並且在物件釋放之後,weak指標被置為nil,好吧!這個都知道,但是內部具體是怎麼實現的呢?
weak
開啟objc-weak.h檔案可以看到以下程式碼
1 2 3 4 5 6 |
struct weak_table_t { weak_entry_t *weak_entries; size_t num_entries; uintptr_t mask; uintptr_t max_hash_displacement; }; |
weak_table_t結構體儲存了與物件弱引用相關的資訊,weak_entry_t是負責來儲存物件弱引用關係的雜湊表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/** * The internal structure stored in the weak references table. * It maintains and stores * a hash set of weak references pointing to an object. * If out_of_line==0, the set is instead a small inline array. */ #define WEAK_INLINE_COUNT 4 struct weak_entry_t { DisguisedPtr referent; union { struct { weak_referrer_t *referrers; uintptr_t out_of_line : 1; uintptr_t num_refs : PTR_MINUS_1; uintptr_t mask; uintptr_t max_hash_displacement; }; struct { // out_of_line=0 is LSB of one of these (don't care which) weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; }; }; }; |
其中referent是被引用物件,union儲存了指向該物件的weak指標。由註釋可以知道,如果out_of_line等於0的時候,hash表被一個陣列所代替。
然後看一下,weak變數到底是怎麼初始化的,這個hash表又是怎麼利用起來的。
1 |
id __weak obj1 = obj; |
當我們初始化一個weak變數的時候,runtime會呼叫objc_initWeak函式
1 2 3 4 5 6 7 8 9 10 |
objc_initWeak(id *location, id newObj) { if (!newObj) { *location = nil; return nil; } return storeWeak (location, (objc_object*)newObj); } |
location代表的是_weak修飾的指標,而newObj則是一個物件。會首先進行一個判斷,如果newObj是一個空指標或者所指向的物件已經釋放了,那麼就會直接返回nil,也就是_weak指標變為nil
如果newObj是一個有效的物件,就會呼叫storeWeak方法,對原始碼整理了之後,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
storeWeak(id *location, objc_object *newObj) { assert(HaveOld || HaveNew); if (!HaveNew) assert(newObj == nil); Class previouslyInitializedClass = nil; id oldObj; SideTable *oldTable; SideTable *newTable; retry: if (HaveOld) { oldObj = *location; oldTable = &SideTables()[oldObj]; } else { oldTable = nil; } if (HaveNew) { newTable = &SideTables()[newObj]; } else { newTable = nil; } SideTable::lockTwo(oldTable, newTable); if (HaveOld && *location != oldObj) { SideTable::unlockTwo(oldTable, newTable); goto retry; } if (HaveNew && newObj) { Class cls = newObj->getIsa(); if (cls != previouslyInitializedClass && !((objc_class *)cls)->isInitialized()) { SideTable::unlockTwo(oldTable, newTable); _class_initialize(_class_getNonMetaClass(cls, (id)newObj)); previouslyInitializedClass = cls; goto retry; } if (HaveOld) { weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); } if (HaveNew) { newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location, CrashIfDeallocating); if (newObj && !newObj->isTaggedPointer()) { newObj->setWeaklyReferenced_nolock(); } *location = (id)newObj; } else { } SideTable::unlockTwo(oldTable, newTable); return (id)newObj; } |
有點長呀!首先判斷是否存在weak指標以前是否指向舊物件,如果存在舊物件,就根據weak指標找到舊物件,並獲取舊物件的sideTable物件
1 2 3 4 5 6 |
if (HaveOld) { oldObj = *location; oldTable = &SideTables()[oldObj]; } else { oldTable = nil; } |
獲取新物件的sideTable物件
1 2 3 4 5 |
if (HaveNew) { newTable = &SideTables()[newObj]; } else { newTable = nil; } |
然後就是在老物件的weak表中移除此weak變數的資訊,在新物件的weak表中與當前weak變數建立關係
1 2 3 4 5 6 7 8 9 10 11 12 |
if (HaveOld) { weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); } if (HaveNew) { newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location, CrashIfDeallocating); if (newObj && !newObj->isTaggedPointer()) { newObj->setWeaklyReferenced_nolock(); } *location = (id)newObj; } |
最後讓_weak指標指向新物件,並返回 新物件
1 2 |
*location = (id)newObj; return (id)newObj; |
Strong
談到weak總會帶上strong,那也說一點吧!可以從NSObject.mm中看到objc_storeStrong的程式碼,也就是對當前指標所指向新舊物件的計數表進行操作
1 2 3 4 5 6 7 8 9 10 |
objc_storeStrong(id *location, id obj) { id prev = *location; if (obj == prev) { return; } objc_retain(obj); *location = obj; objc_release(prev); } |
也就是根據當前strong
指標指向的位置找到舊物件,然後對舊物件執行release
操作,對新物件執行retain
操作,並把strong
指標從新指向新物件。retain
與release
背後其實就是對引用計數的操作,下次再深入分析。
如果有理解錯誤,望留言告知