iOS學習
- 前言
- sideTable
- SlideTables
- SideTableBuf
- SideTable
前言
我們在上一篇中,簡單的介紹了weak的實現原理。其中弱引用表就是存儲在SideTable中的,這里我們來學習了解一下SideTable
sideTable
sideTable主要用于存儲和管理對象的額外信息,特別是弱引用相關的數據。該表的設計和使用時OC運行時實現弱引用的基礎,使得ARC能夠正確的處理弱引用的生命周期。
SlideTables
定義:
static StripedMap<SideTable>& SideTables() {return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
SideTables的實質類型時StripedMap。在StripedMap類中有StripeCount定義存儲sidetable的最大數量。所以每個SideTable可以對應多個對象,而每個對象對應一個sideTable。
SideTableBuf
// We cannot use a C++ static initializer to initialize SideTables because
// libc calls us before our C++ initializers run. We also don't want a global
// pointer to this struct because of the extra indirection.
// Do it the hard way.alignas(StripedMap<SideTable>) static uint8_t SideTableBuf[sizeof(StripedMap<SideTable>)];
- SideTables 在 C++ 的 initializers 函數之前被調用,所以不能使用 C++ 初始化函數來初始化 SideTables,而 SideTables 本質就是 SideTableBuf;
- 不能使用全局指針來指向這個結構體,因為涉及到重定向問題;
而SideTableBuf本質上就是一個長度為Sizeof(StripedMap)的char類型的數組;所以有:
SideTableBuf 本質上就是一個大小為和 StripedMap<SideTable>
對象一致的內存塊;
這也是為什么 SideTableBuf 可以用來表示 StripedMap<SideTable>
對象。本質上而言,SideTableBuf
就是指一個 StripedMap<SideTable>
對象;
StripedMap < SideTable >
StripedMap是一個模板類,該類中有一個array成員,用來存儲PaddedT對象,并且其中對于[]符號的重載定義中,會返回這個PaddedT的value成員,這個value就是我們傳入的T泛型成員,也就是Side Table對象。在array的下標中,這里使用了indexForPointer方法通過位運算計算下標,實現了靜態的Hash Table。而在weak_table中,其成員weak_entry會將傳入對象的地址加以封裝起來,并且其中也有訪問全局弱引用表的入口。
T
是模板類型參數(泛型),它代表 “任意類型”,具體類型由使用 StripedMap
時指定。此處就是SideTable
T
:泛型參數,在運行時中實際代表SideTable
,讓StripedMap
成為管理SideTable
的通用容器。PaddedT
:對T
(即SideTable
)的包裝,通過內存對齊(alignas
)避免多線程訪問時的 CPU 緩存沖突,提升性能。
template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATORenum { StripeCount = 8 };
#elseenum { StripeCount = 64 };
#endifstruct PaddedT {T value alignas(CacheLineSize);};PaddedT array[StripeCount];static unsigned int indexForPointer(const void *p) {uintptr_t addr = reinterpret_cast<uintptr_t>(p);return ((addr >> 4) ^ (addr >> 9)) % StripeCount;}public:T& operator[] (const void *p) { return array[indexForPointer(p)].value; }const T& operator[] (const void *p) const { return const_cast<StripedMap<T>>(this)[p]; }...省略了對象方法...
}
-
首先根據是否為 iphone 定義了一個 StripeCount,iphone 下為 8;即最多為八個sidetable
-
源碼中 CacheLineSize 為 64,使用 T 定義了一個結構體,而 T 就是 SideTable 類型;
-
生成了一個長度為 8 類型為 SideTable 的數組;
-
indexForPointer() 邏輯為根據傳入的指針,經過一定的算法,計算出一個存儲該指針的位置,因為使用了取模運算,所以值的范圍是 0 ~ (StripeCount-1),所以不會出現數組越界;
-
后面的 operator 表示重寫了運算符 [] 的邏輯,調用了 indexForPointer() 方法,這樣使用起來更像一個數組;
SideTables可以理解成一個類型為StripeMap< Side Table>靜態全局對象,內部以數組的形式存儲了StripeCount個SideTable
SideTable
struct SideTable {
// 保證原子操作的自旋鎖 spinlock_t slock;
// 引用計數的 hash 表RefcountMap refcnts;
// weak 引用全局 hash 表weak_table_t weak_table;//構造函數SideTable() {memset(&weak_table, 0, sizeof(weak_table));}//析構函數~SideTable() {_objc_fatal("Do not delete SideTable.");}...省略對象方法...
}
slock是一個自旋鎖,就是為了保證多線程訪問安全性
refcnts本質是一個存儲對象引用計數的hash表,key為對象,value為引用計數(優化過得isa中,引用計數主要存在isa中)
weak_table是存儲對象弱引用的一個結構體,該結構體內的成員如下
/**全局的弱引用表, 保存object作為key, weak_entry_t作為value* The global weak references table. Stores object ids as keys,* and weak_entry_t structs as their values.*/
struct weak_table_t {// 保存了所有指向特地對象的 weak指針集合weak_entry_t *weak_entries;// weak_table_t中有多少個weak_entry_tsize_t num_entries;// weak_entry_t數組的countuintptr_t mask;// hash key 最大偏移值,// 采用了開放定制法解決hash沖突,超過max_hash_displacement說明weak_table_t中不存在要找的weak_entry_tuintptr_t max_hash_displacement;
};
下面是refcnts的定義:
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,RefcountMapValuePurgeable> RefcountMap;
DenseMap
是一個 hash Map,基類 DenseMapBase
中的部分代碼,如下,DenseMapBase
中重寫了操作符 []:
ValueT &operator[](const KeyT &Key) {return FindAndConstruct(Key).second;}
通過傳入的 Key 尋找對應的 Value。而 Key 是 DisguisedPtr<objc_object>
類型,Value 是 size_t
類型。即使用 obj.address :refCount 的形式來記錄引用計數器;
回到最初的 sidetable_addExtraRC_nolock
方法中:
size_t& refcntStorage = table.refcnts[this];
通過 this
,即 object 對象的地址,取出 refcnts
這個哈希表中存儲的引用計數器;
refcnts 可以理解成一個 Map,使用 address:refcount 的形式存儲了很多個對象的引用計數器;看不太懂這里
總結一下吧
- iphone中Side Tables()本質上返回一個Side TableBuf對象,該對象存儲8個SideTable;(StripeCount)
- 涉及到多線程和效率問題,有多個SideTable來存儲對象相關的引用計數器和弱引用
- Apple通過對object的地址進行運算之后,對Side Table的個數進行取模運算,以次來決定將對象分配到哪個SideTable進行信息存儲,因為有取模運算,所以不會出現數組溢出。范圍為0-StripeCount-1
- 當對象需要使用到Side Table時,會被分配到到 8/64 個全局 sideTables 中的某一個表中存儲相關的引用計數器或者弱引用信息;
這里再附上一張上一篇的弱引用表關系圖: