準備工作
- 請準備好750.1版本的objc4源碼一份【目前最新的版本】,打開它玫恳,找到文章中提到的方法捌治,類型,對象
- 一切請以手中源碼為準纽窟,不要輕信任何人肖油,任何文章,包括本篇博客
- 文章中的源碼都請過了我的刪改臂港,建議還是先看看源碼
- 源碼建議從Apple官方開源網(wǎng)站獲取obj4
- 官網(wǎng)上下載下來需要自己配置才能編譯運行森枪,如果不想配置,可以在RuntimeSourceCode中clone
數(shù)據(jù)模型
SideTables()
- SideTables()是一個全局的哈希表审孽,里面存儲了sideTable結(jié)構(gòu)體县袱,根據(jù)對象的地址可以在sidetables()找到相應的SideTable
- SideTables()的定義:
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
- 可以大致理解為,該方法會返回一個StripedMap類型的數(shù)據(jù)結(jié)構(gòu)佑力,里面存儲的類型是SideTable
StripedMap
template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
struct PaddedT {
T value alignas(CacheLineSize);
//enum { CacheLineSize = 64 };
};
PaddedT array[StripeCount];
//enum { StripeCount = 64 };
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];
}
};
- 這里面其實StripedMap里一共就一個PaddedT類型的數(shù)組式散,大小為StripeCount【64】
- SideTables() 返回的 StripedMap, 是一個 value 為 SideTable 的哈希桶(由于 SideTable 內(nèi)部又在維護數(shù)組, 所以這是一個哈希桶結(jié)構(gòu)), 哈希值由對象的地址計算得出
補充知識:哈希桶
- 哈希桶算法就是鏈地址解決沖突的方法
- 所謂哈希桶,以SideTables與SideTable的方式來舉例的話打颤,就是本來數(shù)據(jù)數(shù)量不多的時候暴拄,只要有個SideTables就行,但由于數(shù)據(jù)數(shù)量超過了64【SideTables內(nèi)部數(shù)組的長度】编饺,導致出現(xiàn)了哈希沖突乖篷,兩個對象都有f(key),需要占用同一個空間
- 哈希桶就是將這個數(shù)組的單個空間擴展成一個拉鏈一樣的結(jié)構(gòu)透且,將指向同一個空間的值存到同一條拉鏈上
- 這里撕蔼,SideTable就是一條拉鏈,一個對象根據(jù)其地址在SideTables上找到相應的SideTable秽誊,存入其中
- 拉鏈可以用數(shù)組/鏈表組織數(shù)據(jù)鲸沮,Apple使用的是數(shù)組
SideTable
- SideTable的定義一點都不長,卻很麻煩
struct SideTable {
spinlock_t slock; //自旋鎖
RefcountMap refcnts; //存放引用計數(shù)
weak_table_t weak_table; //weak_table是一個哈希
};
- 這里先來看這三兄弟是干什么的
spinlock_t slock【自旋鎖】
補充知識:鎖
- 鎖是操作系統(tǒng)中的一個概念
- 為了保證數(shù)據(jù)的一致性锅论,在多線程編程中我們會用到鎖讼溺,使得在某一時間點,只有一個線程進入臨界區(qū)代碼棍厌。雖然不同的語言可能會提供不同的鎖接口肾胯,但是底層調(diào)用的都是操作系統(tǒng)的提供的鎖,不同的高級語言只是在操作系統(tǒng)的鎖機制基礎上進行了些封裝而已耘纱,要真正理解鎖敬肚,還是得看操作系統(tǒng)是怎么實現(xiàn)鎖的。
- 以引用計數(shù)舉例束析,由于我們的程序是多線程的艳馒,可能在不同的線程都會對某個對象的應用計數(shù)操作,導致混亂
- 這里使用鎖员寇,當某次訪問開始時弄慰,鎖就被持有,其余訪問就不能進行蝶锋,當訪問結(jié)束陆爽,鎖被空出來,才能輪到其他訪問去競爭這把鎖
RefcountMap refcnts【存放引用計數(shù)】
引用計數(shù)存在RefcountMap結(jié)構(gòu)里扳缕,也就是上文介紹哈希桶的時候提到的拉鏈
這也是個hash結(jié)構(gòu)慌闭,根據(jù)對象地址存儲引用計數(shù)
//RefcountMap
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;
//DenseMap
template<typename KeyT, typename ValueT,
bool ZeroValuesArePurgeable = false,
typename KeyInfoT = DenseMapInfo<KeyT> >
class DenseMap
: public DenseMapBase<DenseMap<KeyT, ValueT, ZeroValuesArePurgeable, KeyInfoT>,
KeyT, ValueT, KeyInfoT, ZeroValuesArePurgeable> {
typedef DenseMapBase<DenseMap, KeyT, ValueT, KeyInfoT, ZeroValuesArePurgeable> BaseT;
//ZeroValuesArePurgeable:ZeroValuesArePurgeable 默認值是 false, 但 RefcountMap 指定其初始化為 true. 這個成員標記是否可以使用值為 0 (引用計數(shù)為 1) 的桶.
typedef typename BaseT::BucketT BucketT;
friend class DenseMapBase<DenseMap, KeyT, ValueT, KeyInfoT, ZeroValuesArePurgeable>;
BucketT *Buckets;
//Buckets 指針管理一段連續(xù)內(nèi)存空間, 也就是數(shù)組, 數(shù)組成員是 BucketT 類型的對象桶的 key 為 EmptyKey 時是空桶
//typedef std::pair<KeyT, ValueT> BucketT;
//桶的數(shù)據(jù)類型std::pair,將對象地址和對象的引用計數(shù)(這里的引用計數(shù)類似于 isa, 也是使用其中的幾個 bit 來保存引用計數(shù), 留出幾個 bit 來做其它標記位)組合成一個數(shù)據(jù)類型.
unsigned NumEntries;
//NumEntries 記錄數(shù)組中已使用的非空的桶的個數(shù).
unsigned NumTombstones;
//當一個對象的引用計數(shù)為0, 要從桶中取出時, 其所處的位置會被標記為 TombstoneNumTombstones 就是數(shù)組中的墓碑的個數(shù). 后面會介紹到墓碑的作用
unsigned NumBuckets;
//NumBuckets 桶的數(shù)量, 因為數(shù)組中始終都充滿桶, 所以可以理解為數(shù)組大小.
}
DisguisedPtr
- DisguisedPtr是runtime對于普通對象指針(引用)的一個封裝躯舔,目的在于隱藏weak_table_t的內(nèi)部指針驴剔。
- 對于這個我了解的也不是很清楚,大概理解就是通過static uintptr_t disguise(T* ptr) {return -(uintptr_t)ptr;}來實現(xiàn)隱藏
DenseMap
- 當你進入DenseMap定義的時候粥庄,抬頭看下最上方的文件名丧失,發(fā)現(xiàn)已經(jīng)離開objc4了,進入了一個叫l(wèi)lvm-DenseMap.h的里世界惜互,這里面的代碼也是開著全局搜索也搜不到的神仙代碼【但很奇怪布讹,這里還是可以編輯】
補充知識:LLVM
- LLVM是構(gòu)架編譯器(compiler)的框架系統(tǒng),以C++編寫而成训堆,用于優(yōu)化以任意程序語言編寫的程序的編譯時間(compile-time)炒事、鏈接時間(link-time)、運行時間(run-time)以及空閑時間(idle-time)蔫慧,對開發(fā)者保持開放挠乳,并兼容已有腳本。
- LLVM計劃啟動于2000年姑躲,最初由美國UIUC大學的Chris Lattner博士主持開展睡扬。2006年Chris Lattner加盟Apple Inc.并致力于LLVM在Apple開發(fā)體系中的應用。Apple也是LLVM計劃的主要資助者黍析。
- 目前LLVM已經(jīng)被蘋果IOS開發(fā)工具卖怜、Xilinx Vivado、Facebook阐枣、Google等各大公司采用马靠。
- 簡單來說奄抽,把這里的理解成某個第三方庫就行
- 這個DenseMap中的操作我只不講源碼,只分析其流程甩鳄,感興趣可以自己深入
哈希實現(xiàn)
這個DenseMap本質(zhì)上是std::pair<KeyT, ValueT>逞度,C++中的pair類型,反正也是個鍵值對
DenseMap同樣是個hash結(jié)構(gòu)妙啃,下面來分析它的流程【很多參數(shù)的解釋我都在代碼注釋里寫了】
這里借用下runtime(二) SideTables大神的圖解完全講清楚了問題档泽,厲害
首先我們有一個初始化好的, 大小為 9 的桶數(shù)組
- 同時有 a b c d e 五個對象要使用桶數(shù)組, 這里我們假設五個對象都被哈希算法分配到下標 0 的位置里. a 第一個進入, 但 b c d e 由于下標 0 處已經(jīng)不是空桶, 則需要進行下一步哈希算法來查找合適的位置, 假設這 4 個對象又恰巧都被分配到了下標為 1 的位置, 但只有 b 可以存入. 假設每一次哈希計算都只給下標增加了 1, 以此類推我們能得到:
- 假設這個時候 c 對象被釋放了, 之前提到過這個時候會把對應的位置的 key 設置為 TombstoneKey:
- 接下來就體現(xiàn)了墓碑的作用:
- 如果 c 對象銷毀后將下標 2 的桶設置為空桶, 此時為 e 對象增加引用計數(shù), 根據(jù)哈希算法查找到下標為 2 的桶時, 就會直接插入, 無法為已經(jīng)在下標為 4 的桶中的 e 增加引用計數(shù).
- 如果此時初始化了一個新的對象 f, 根據(jù)哈希算法查找到下標為 2 的桶時發(fā)現(xiàn)桶中放置了墓碑, 此時會記錄下來下標 2. 接下來繼續(xù)哈希算法查找位置, 查找到空桶時, 就證明表中沒有對象 f, 此時 f 使用記錄好的下標 2 的桶而不是查找到的空桶, 就可以利用到已經(jīng)釋放的位置.
- 對于DenseMap的分析我們就到這
weak_table_t weak_table
/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*/
struct weak_table_t {
weak_entry_t *weak_entries; //管理所有指向某對象的weak指針,也是一個hash
size_t num_entries; //數(shù)組中已占用位置的個數(shù)
uintptr_t mask; //數(shù)組下標最大值(即數(shù)組大小 -1)
uintptr_t max_hash_displacement; //最大哈希偏移值
};
- weak_table并沒有直接是個數(shù)組存放weak指針揖赴,而是使用一個結(jié)構(gòu)體weak_entry_t *weak_entries;去存放weak指針
weak_entry_t *weak_entries【存放weak指針】
struct weak_entry_t {
DisguisedPtr<objc_object> referent; //被指對象的地址馆匿。前面循環(huán)遍歷查找的時候就是判斷目標地址是否和他相等。
union {
struct {
weak_referrer_t *referrers; //可變數(shù)組,里面保存著所有指向這個對象的弱引用的地址燥滑。當這個對象被釋放的時候渐北,referrers里的所有指針都會被設置成nil。
//指向 referent 對象的 weak 指針數(shù)組
uintptr_t out_of_line_ness : 2; //這里標記是否超過內(nèi)聯(lián)邊界, 下面會提到
uintptr_t num_refs : PTR_MINUS_2; //數(shù)組中已占用的大小
uintptr_t mask; //數(shù)組下標最大值(數(shù)組大小 - 1)
uintptr_t max_hash_displacement; //最大哈希偏移值
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; //只有4個元素的數(shù)組铭拧,默認情況下用它來存儲弱引用的指針腔稀。當大于4個的時候使用referrers來存儲指針。
//當指向這個對象的weak指針不超過4個羽历,則直接使用數(shù)組inline_referrers焊虏,省去hash
};
};
這里有見到了我們的老朋友union,也就提示我們Apple同樣是使用過同一段內(nèi)存去存放不同的信息
這里有兩個數(shù)組秕磷,一個是weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]诵闭,數(shù)組長度只有4,當某個對象的weak指針個數(shù)小于四的時候澎嚣,會存入這個數(shù)組疏尿,當大于四的時候才會存到referrers當中
至此,所有的數(shù)據(jù)模型總結(jié)完畢
是不是感覺有點懵易桃?
結(jié)構(gòu)總結(jié)
- 總之只要是數(shù)組的基本都是哈希結(jié)構(gòu)
SideTables以及SideTable的關系
- 這里借用下iOS管理對象內(nèi)存的數(shù)據(jù)結(jié)構(gòu)以及操作算法--SideTables褥琐、RefcountMap、weak_table_t-二大神的比喻
- 假設有80個學生需要咱們安排住宿晤郑,同時還要保證學生們的財產(chǎn)安全敌呈。應該怎么安排?
- 顯然不會給80個學生分別安排80間宿舍造寝,然后給每個宿舍的大門上加一把鎖磕洪。那樣太浪費資源了鎖也挺貴的,太多的宿舍維護起來也很費力氣诫龙。
- 我們一般的做法是把80個學生分配到10間宿舍里析显,每個宿舍住8個人。假設宿舍號分別是101签赃、102 谷异、... 110分尸。然后再給他們分配床位,01號床歹嘹、02號床等箩绍。然后給每個宿舍配一把鎖來保護宿舍內(nèi)同學的財產(chǎn)安全。為什么不只給整個宿舍樓上一把鎖荞下,每次有人進出的時候都把整個宿舍樓鎖上伶选?顯然這樣會造成宿舍樓大門口阻塞史飞。
- OK假如現(xiàn)在有人要找102號宿舍的2號床的人聊天尖昏。這個人會怎么做? 1构资、找到宿舍樓(SideTables)的宿管抽诉,跟他說自己要找10202(內(nèi)存地址當做key)。 2吐绵、宿管帶著他SideTables[10202]找到了102宿舍SideTable迹淌,然后把102的門一鎖lock,在他訪問102期間不再允許其他訪客訪問102了己单。(這樣只是阻塞了102的8個兄弟的訪問唉窃,而不會影響整棟宿舍樓的訪問) 3、然后在宿舍里大喊一聲:"2號床的兄弟在哪里纹笼?"table.refcnts.find(02)你就可以找到2號床的兄弟了纹份。 4、等這個訪客離開的時候會把房門的鎖打開unlock廷痘,這樣其他需要訪問102的人就可以繼續(xù)進來訪問了蔓涧。
- SideTables == 宿舍樓;SideTable == 宿舍笋额;RefcountMap里存放著具體的床位
- 蘋果之所以需要創(chuàng)造SideTables的Hash沖突是為了把對象放到宿舍里管理元暴,把鎖的粒度縮小到一個宿舍SideTable。RefcountMap的工作是在找到宿舍以后幫助大家找到正確的床位的兄弟兄猩。
- 討論完這些問題之后茉盏,我們終于可以開始研究weak調(diào)用的方法了
weak相關的方法
objc_initWeak(id *location, id newObj)
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
- 該方法中的兩個參數(shù)location是指weak 指針,newObj 是 weak 指針將要指向的對象
storeWeak(id *location, objc_object *newObj)
id oldObj;
SideTable *oldTable;
SideTable *newTable;
// Clean up old value, if any.
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
if (haveNew) {
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
return (id)newObj;
- 這里要先進行haveOld判斷枢冤,也就是如果該指針有指向的舊值援岩,先要weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);處理舊值
- 之后在weak_register_no_lock(&newTable->weak_table, (id)newObj, location,crashIfDeallocating);進行賦值操作
weak_unregister_no_lock
//從原有的表中先刪除這個 weak 指針注銷已注冊的弱引用。當引用者的存儲即將消失掏导,但引用還沒有死享怀。(否則,稍后歸零引用將是內(nèi)存訪問錯誤趟咆。)如果引用/引用不是當前活動的弱引用添瓷,則不執(zhí)行任何操作梅屉。引用不為零。
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id)
{
objc_object *referent = (objc_object *)referent_id; //weak 指針指向的對象
objc_object **referrer = (objc_object **)referrer_id; //referrer_id是 weak 指針, 操作時需要用到這個指針的地址
weak_entry_t *entry;
if (!referent) return;
if ((entry = weak_entry_for_referent(weak_table, referent))) { //查找 referent 對象對應的 entry
remove_referrer(entry, referrer); //從 referent 對應的 entry 中刪除地址為 referrer 的 weak 指針
bool empty = true;
if (entry->out_of_line() && entry->num_refs != 0) { //如果 entry 中的數(shù)組容量大于 4 并且數(shù)組中還有元素
empty = false; //entry 非空
}
else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) { //否則循環(huán)查找 entry 數(shù)組, 如果 4 個位置中有一個非空
empty = false; //entry 非空
break;
}
}
}
if (empty) {
weak_entry_remove(weak_table, entry); //從 weak_table 中移除該條 entry
}
}
}
- 這里先是remove_referrer(entry, referrer);從對應的entry中刪除該weak記錄
- 之后我們需要判斷鳞贷,還有沒有指向這個地址的weak指針了坯汤,所以有下面的判空操作,如果這個entry已經(jīng)沒有東西了搀愧,將整個刪除
weak_register_no_lock
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
// now remember it and where it is being stored
weak_entry_t *entry; //如果 weak_table 有對應的 entry
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer); //將 weak 指針存入對應的 entry 中
}
else {
weak_entry_t new_entry(referent, referrer); //創(chuàng)建新的 entry
weak_grow_maybe(weak_table); //查看是否需要調(diào)整 weak_table 中 weak_entries 數(shù)組大小
weak_entry_insert(weak_table, &new_entry); //將新的 entry 插入到 weak_table 中
}
return referent_id;
}
這里如果該地址有對應的entry惰聂,在里面存入指針;如果沒有entry咱筛,就新建entry搓幌,在里面插入weak指針,并插入weak_table
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
該方法通過對象的地址迅箩,獲取到該weak_table中的entry【保存著指向某對象的所有weak指針】
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
assert(referent);
weak_entry_t *weak_entries = weak_table->weak_entries;
if (!weak_entries) return nil;
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_table->weak_entries);
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
return &weak_table->weak_entries[index];
}
hash_pointer也是使用指針地址溉愁,映射到一個索引。&weak_table->mask這個操作是饲趋?這個mask實際值是表的size-1,而size是2的n次方進行擴張的拐揭,所以mask的形式就1111 1111 1111這種,索引和mask位與之后的值必定就落在了[0, size]范圍內(nèi)奕塑。也就是說堂污,先是通過hash_pointer對地址進行hash映射,得到下標龄砰,接下來通過位遮蔽盟猖,保證這個映射是在范圍內(nèi)的,不會超出范圍
index = (index+1) & weak_table->mask;是在遇到哈希沖突的時候寝贡,就一直往下找下一個位置
下面我們看下append_referrer的實現(xiàn)
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
if (! entry->out_of_line()) { //如果數(shù)組大小沒超過 4
// Try to insert inline.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) { //循環(huán)查找數(shù)組成員
entry->inline_referrers[i] = new_referrer; //把新的 weak 指針插入到空位置
return;
}
}
//也就是說走到這就是剛好之前有四個引用扒披,現(xiàn)在添加第五個
//數(shù)組中的 4 個位置都非空, 就要調(diào)整策略使用 referrers 了
//從這里開始, 這一段是把 inline_referrers 數(shù)組調(diào)整為使用 referrers 的形式
// Couldn't insert inline. Allocate out of line.
weak_referrer_t *new_referrers = (weak_referrer_t *)
calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t)); //還是開辟 4 個 weak_referrer_t 大小的空間
// This constructed table is invalid, but grow_refs_and_insert
// will fix it and rehash it.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
new_referrers[i] = entry->inline_referrers[i]; //將 inline_referrers 中的值賦值給 referrers
}
entry->referrers = new_referrers;
entry->num_refs = WEAK_INLINE_COUNT;
entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
entry->mask = WEAK_INLINE_COUNT-1;
entry->max_hash_displacement = 0;
}
assert(entry->out_of_line());
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
return grow_refs_and_insert(entry, new_referrer);
}
//開始哈希算法
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
size_t index = begin; //使用哈希算法計算到一個起始下標
size_t hash_displacement = 0; //哈希偏移次數(shù)
while (entry->referrers[index] != nil) { //循環(huán)找空位置
hash_displacement++;
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
}
//這里記錄下移位的最大值, 那么數(shù)組里的任何一個數(shù)據(jù), 存儲時的移位次數(shù)都不大于這個值
//可以提升查找時的效率, 如果移位次數(shù)超過了這個值都沒有找到, 就證明要查找的項不在數(shù)組中
if (hash_displacement > entry->max_hash_displacement) {
entry->max_hash_displacement = hash_displacement;
}
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
}
- 這里首先是要判斷關聯(lián)數(shù)組里有沒有空位【關聯(lián)數(shù)組沒有使用哈希】圃泡,如果關聯(lián)數(shù)組有空位的話碟案,直接插入
- 沒有空位需要使用且是剛好第五個的情況,需要初始化一個weak_referrer_t颇蜡,將關聯(lián)數(shù)組里的數(shù)據(jù)先存進去【也就是說不會兩個一起用】
- 接下來通過哈希操作存入
賦值操作
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
- 這個賦值語句之前一直讓我百思不得其解价说,現(xiàn)在感覺還是C語言學的不到位呀
- 首先,現(xiàn)在的index就是我們正確的插入下標风秤,按照我們正常人明顯應該這么寫
entry->referrers[index] = new_referrer;
entry->num_refs++;
- 這是Apple偏偏沒有這么寫鳖目,寫了上面這三行
- 說來慚愧,我之前都覺得weak_referrer_t &ref = entry->referrers[index];明顯是錯的缤弦,哪有初始化一個地址這個說法的
- 現(xiàn)在我的理解可能依然不太對领迈,我覺得是這樣,ref依然是個weak_referrer_t類型的變量,現(xiàn)在通過&取地址符狸捅,設置這個變量的地址【這里就覺得很迷了衷蜓,一個結(jié)構(gòu)體的地址怎么可以變呢?或者理解成ref不是一個實際上的變量尘喝,只是一個標志符】
- 現(xiàn)在我們把referrers[index]賦給了這個地址磁浇,也就相當于ref現(xiàn)在就是referrers[index]【自己都覺得解釋的很不清楚。朽褪。置吓。】
- 放一段自己做的實驗
#include <iostream>
using namespace std;
struct Padd {
int a;
float b;
}bucket[10];
int main(int argc, const char * argv[]) {
// insert code here...
std::cout << "Hello, World!\n";
int j = 0;
float k = 16;
for (Padd &i : bucket) {
i.a = j++;
i.b = k++;
}
for (Padd &i : bucket) {
cout << i.a << "XXX" << i.b << endl;
}
Padd *testPadd = (Padd *)malloc(sizeof(Padd));
testPadd->a = 99;
testPadd->b = 109;
Padd &ref = bucket[4];
ref = *testPadd;
printf("%p\n", &bucket[4]);
printf("%p\n", &ref);
for (Padd &i : bucket) {
cout << i.a << "XXX" << i.b << endl;
}
return 0;
}
dealloc后將weak指針置nil【weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 】
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
return;
}
weak_referrer_t *referrers;
size_t count;
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
}
}
weak_entry_remove(weak_table, entry);
}
- 前面將關聯(lián)數(shù)組和referrers都看作一個數(shù)組,獲取相應的count,進行nil操作
方法總結(jié)
后續(xù)更新
對于weak對象的訪問:objc_loadWeakRetained
- 先回看下《iOS高級編程》中的描述
id __weak obj1 = obj0;
NSLog(@"class = %@",[obj1 class]);
id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"class = %@",[tmp class]);//實際訪問的是注冊到自動釋放池的對象
- 現(xiàn)在我們有源碼了劣挫,可以看看具體是怎么實現(xiàn)的
//實驗代碼
NSObject *obj0 = [[NSObject alloc] init];
NSObject *obj1 __weak = obj0;
printf("retain count = %ld\n",CFGetRetainCount((__bridge CFTypeRef)(obj0))); //結(jié)果為1
printf("retain count = %ld\n",CFGetRetainCount((__bridge CFTypeRef)(obj1))); //斷點打在這里 結(jié)果為2
- 接著跟著方法調(diào)用棧往下走,發(fā)現(xiàn)來到objc_loadWeakRetained
id
objc_loadWeakRetained(id *location)
{
id obj;
id result;
Class cls;
SideTable *table;
retry:
// fixme std::atomic this load
obj = *location;
if (!obj) return nil;
if (obj->isTaggedPointer()) return obj;
table = &SideTables()[obj];
table->lock();
if (*location != obj) {
table->unlock();
goto retry;
}
result = obj;
cls = obj->ISA();
if (! cls->hasCustomRR()) {
// Fast case. We know +initialize is complete because
// default-RR can never be set before then.
assert(cls->isInitialized());
if (! obj->rootTryRetain()) { //rootTryRetain執(zhí)行了retain操作
result = nil;
}
}
table->unlock();
return result;
}
- 這過方法里的 if (! obj->rootTryRetain())這一句調(diào)用了retain方法构拳,達到了引用計數(shù)+1咆爽,防止dealloc的作用
- 接著我們給objc_release方法打上斷點梁棠,發(fā)現(xiàn)這句打印一結(jié)束,就會調(diào)用release方法【是誰調(diào)用的斗埂,為什么會調(diào)用的符糊,我是一問三不知】
- 也就是說對于weak修飾對象的訪問都會調(diào)用該方法,進行retain【包括NSObject *obj0 = [[NSObject alloc] init];NSObject *obj1 __weak = obj0;NSObject *obj2 = obj1;中的第三句呛凶,也算是訪問】
- 因此男娄,書上說的autorelease的方式并不是目前真正的實現(xiàn)方式了
- 我個人認為這種方式還要比autorelease更好理解些
isa_t中存放的引用計數(shù)
- isa_t中有這兩塊內(nèi)容,uintptr_t has_sidetable_rc【是否使用sidetable來存放引用計數(shù)】漾稀,uintptr_t extra_rc【引用計數(shù)】
- 也就是說對于能存的下的引用計數(shù)是通過isa來存儲的
- 這里我們可觀察下retainCount方法來了解下這兩塊之間的關系
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) {
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
- 哇模闲,真的是好久沒看到這么簡單易懂的源碼,在經(jīng)歷了各色妖魔鬼怪崭捍,現(xiàn)在這種跟幼兒讀物一樣的源碼我們讀起來完全跟割黃油一樣easy尸折,絲滑<g-emoji class="g-emoji" alias="v" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/270c.png" style="box-sizing: border-box; font-family: "Apple Color Emoji", "Segoe UI", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 1.2em; font-weight: 400; line-height: 20px; vertical-align: middle; font-style: normal !important; margin-right: 3px;">??</g-emoji>
- 也就是說,最后返回的引用計數(shù)會是isa中的加上【如果超過限制】殷蛇,sideTable中的
為什么weak_entries中referrers要使用hash实夹?
我們來理一理思路,在weak_entries中粒梦,已經(jīng)有referent存儲了被指對象的地址亮航,referrers中存放的是所有指向該對象的weak指針的地址
而我們使用了hash的方式存儲,我們將weak指針的地址hash出一個index來存放weak指針的地址
有沒有覺得怪怪的匀们?一般來說我們用key【一般是對象地址】哈希出一個index作為存放地址缴淋,存放的是對象本身,也就是value
但在這里,等于就是用key存key了重抖。因此我之前以為這里沒必要hash圆存,因為假如我們希望查詢到該weak指針的地址,我們得拿weak指針的地址去找仇哆。沦辙。。
但其實這里還是因為沒有很好的理解他的意義讹剔,這張表不是為了查找weak指針的地址而存在的油讯,而是要記錄指向某一對象的所有weak指針而存在的,其存在的意義就是在記錄上有本身
我們查找某一weak指針往往基本上就是為了把記錄這個東西
在這張表上刪除延欠,我們的目的在于記錄本身陌兑,而不是其value本身,以上由捎。