主要參考:
weak 弱引用的實現(xiàn)方式
詳解獲取weak對象的過程
解析:SideTables, SideTable, weak_table, weak_entry_t
weak修飾有什么用?
聲明為weak的指針鞋仍,weak指針指向的對象一旦被釋放艺普,weak的指針都將被賦值為nil
weak的賦值與訪問
NSObject *obj = [[NSObject alloc] init];
// weak的三種賦值情況
// (1)變量賦值
_weakObj = obj; // 編譯為:objc_storeWeak(&_weakObj, obj);
// (2) 直接初始化孵班,strong對象賦值
__weak NSObject *obj1 = obj; // 編譯為:objc_initWeak(&obj1, obj);
// (3) 直接初始化些椒,weak對象賦值
__weak NSObject *obj2 = _weakObj; // 編譯為:objc_copyWeak(&obj2, & _weakObj);
// weak的訪問情況,就是調(diào)用 objc_loadWeakRetained(id *location)
NSLog(@"=====%@",_weakObj);
// 編譯為下面代碼
/*
id temp = objc_loadWeakRetained(&weakObj);
NSLog(@"=====%@",temp);
objc_release(temp);
*/
weak流程圖
NSObject.mm里面的代碼
objc_initWeak:定義并賦值一個weak變量
id
objc_initWeak(id *location, id newObj)
{
// 相比objc_storeWeak就是 多了這個判斷
if (!newObj) {
*location = nil;
return nil;
}
// 這里傳遞了三個 bool 數(shù)值
// 使用 template 進行常量參數(shù)傳遞是為了優(yōu)化性能
// DontHaveOld--沒有舊對象静浴,
// DoHaveNew--有新對象勿她,
// DoCrashIfDeallocating-- 如果釋放了就Crash提示
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
objc_storeWeak:用strong對象賦值
id
objc_storeWeak(id *location, id newObj)
{
return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object *)newObj);
}
objc_copyWeak:用weak對象賦值
void
objc_copyWeak(id *dst, id *src)
{
// 函數(shù)取出附有__weak修飾符變量所引用的對象并retain
id obj = objc_loadWeakRetained(src);// 根據(jù)scr獲取指向的對象obj,retain obj
objc_initWeak(dst, obj); // 調(diào)用objc_initWeak 方法
objc_release(obj); // release obj
}
objc_destroyWeak:weak變量釋放
void
objc_destroyWeak(id *location)
{
(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
(location, nil);
}
主要就兩個方法:
1噪生、設(shè)置方法:storeWeak
2裆赵、讀取方法:objc_loadWeakRetained(ARC)、objc_loadWeak(MRC)
StoreWeak源碼
template <bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj) {
// 斷言判斷
assert(haveOld || haveNew);
if (!haveNew) assert(newObj == nil);
// 臨時記錄這個過程中正在初始化initialize的類
Class previouslyInitializedClass = nil;
id oldObj;
// SideTable就是存儲對象的weak相關(guān)的信息(后面有簡單的說明)
SideTable *oldTable;
SideTable *newTable;
retry:
if (HaveOld) {
oldObj = *location; // 獲取舊對象
oldTable = &SideTables()[oldObj]; // 獲取舊對象的SideTable
} else {
oldTable = nil;
}
if (HaveNew) {
newTable = &SideTables()[newObj];// 獲取新對象的SideTable
} else {
newTable = nil;
}
// 加鎖操作跺嗽,防止多線程中競爭沖突
SideTable::lockTwo<HaveOld, HaveNew>(oldTable, newTable);
// 避免線程沖突重處理
// location 應(yīng)該與 oldObj 保持一致战授,如果不同,說明被其他線程所修改桨嫁,goto retry
if (HaveOld && *location != oldObj) {
SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
goto retry;
}
// 防止弱引用間死鎖
// 并且通過 +initialize 初始化構(gòu)造器保證所有弱引用的 isa 非空指向
if (HaveNew && newObj) {
// 獲得新對象的 isa 指針
Class cls = newObj->getIsa();
// 判斷 isa 非空且已經(jīng)初始化
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized()) {
// 解鎖
SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
// 對其 isa 指針進行初始化
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
// 如果該類已經(jīng)完成執(zhí)行 +initialize 方法是最理想情況
// 如果該類 +initialize 在線程中
// 例如 +initialize 正在調(diào)用 storeWeak 方法
// 需要手動對其增加保護策略植兰,并設(shè)置 previouslyInitializedClass 指針進行標記
previouslyInitializedClass = cls;
// 重新嘗試
goto retry;
}
}
// ??????(2)清除舊對象weak_table種的location
if (HaveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// ??????(3) 保存location到新對象的weak_table種
if (HaveNew) {
newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table,
(id)newObj, location,
CrashIfDeallocating);
// 如果弱引用被釋放 weak_register_no_lock 方法返回 nil
if (newObj && !newObj->isTaggedPointer()) {
// 標記新對象有weak引用说墨,isa.weakly_referenced = true;
newObj->setWeaklyReferenced_nolock();
}
// 設(shè)置location指針指向newObj
*location = (id)newObj;
}
else {
// 沒有新值超升,則無需更改
}
SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
return (id)newObj;
}
objc_loadWeakRetained源碼 (ARC訪問weak變量)
id
objc_loadWeakRetained(id *location)
{
id obj;
id result;
Class cls;
SideTable *table;
retry:
obj = *location; // 獲取指向的對象
if (!obj) return nil;
if (obj->isTaggedPointer()) return obj;
table = &SideTables()[obj];// 獲取對象的SideTable
table->lock(); // 加鎖
if (*location != obj) { // 對比一次萌朱,是在有其他地方在操作修改
table->unlock();
goto retry;
}
result = obj;
cls = obj->ISA();
if (! cls->hasCustomRR()) {
/*
hasCustomRR判斷類或父類是否實現(xiàn)下列方法:
1掂骏、retain/release/autorelease
2耐亏、retainCount/ _tryRetain
3、_isDeallocating
4胚委、retainWeakReference/allowsWeakReference
??????(1)一般不會重寫這些方法竹椒,返回NO,執(zhí)行系統(tǒng)的Retain
*/
assert(cls->isInitialized());
if (! obj->rootTryRetain()) {
// 如果retain失敗膝晾,返回nil
result = nil;
}
}
else {
// ??????(2)有自定義的retain/release
// 先看是否初始化initialize
if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
class_getMethodImplementation(cls, SEL_retainWeakReference);
// 是否實現(xiàn)了retainWeakReference栓始,
if ((IMP)tryRetain == _objc_msgForward) {
result = nil;
}
else if (! (*tryRetain)(obj, SEL_retainWeakReference)) {
// 是否可以retain對象,返回NO血当,該變量將使用“nil”
result = nil;
}
}
else {
// 沒有初始化幻赚,先初始化,goto retry
table->unlock();
_class_initialize(cls);
goto retry;
}
}
table->unlock();
return result;
}
objc_loadWeak (MRC訪問weak變量)
id objc_loadWeak(id *location) {
if (!*location) return nil;
// 利用自動釋放池臊旭,對retain的對象進行release操作
return objc_autorelease(objc_loadWeakRetained(location));
}
SideTable落恼、weak_table_t、weak_entry_t离熏、weak_referrer_t
1佳谦、SideTable:SideTable *objTable = &SideTables()[obj];
從全局的哈希表SideTables中,利用對象本身地址進行位運算后得到對應(yīng)下標滋戳,取得該對象的弱引用表钻蔑。SideTables是一個 64 個元素長度的散列表,發(fā)生碰撞時奸鸯,可能一個SideTable中存在多個對象共享一個弱引用表咪笑。
2、weak_table_t是全部對象weak表:weak_table_t *weak_table = objTable->weak_table;
3娄涩、weak_entry_t是一個對象的信息:weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);(referent就是對象地址)
4窗怒、weak_referrer_t是這個對象所有weak變量的地址表:weak_referrer_t *referrers = entry->referrers;
struct SideTable {
spinlock_t slock; // 保證原子操作的自旋鎖
RefcountMap refcnts; // 引用計數(shù)的 hash 表
weak_table_t weak_table; // weak 引用全局 hash 表
}
struct weak_table_t {
weak_entry_t *weak_entries;// 哈希表結(jié)構(gòu)
size_t num_entries; // weak_entry_t個數(shù)
uintptr_t mask;// 總?cè)萘?1,用來弄哈希值的 (n& mask)就是“除留取余法”(n%總?cè)萘浚? uintptr_t max_hash_displacement;// 碰撞次數(shù)
};
typedef objc_object ** weak_referrer_t;
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
union {
struct {
weak_referrer_t *referrers; // 哈希表結(jié)構(gòu)
uintptr_t out_of_line : 1; // 1是referrers存儲蓄拣,0是inline_referrers
uintptr_t num_refs : PTR_MINUS_1;// 已存referrer總數(shù)
uintptr_t mask; // 總?cè)萘?1兜粘,用來弄哈希值的 (n& mask)就是“除留取余法”(n%總?cè)萘? uintptr_t max_hash_displacement; // 碰撞次數(shù)
};
struct {
// 如果weak變量少,用數(shù)組來存放弯蚜,如果大于4個孔轴,就使用referrers
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
}
}
上面三層結(jié)構(gòu)下來,referent就是對象地址碎捺,referrers是這個對象所有weak變量的地址路鹰,
SideTable *objTable = &SideTables()[obj];
weak_table_t *weak_table = objTable->weak_table;
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
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;
// 對象地址哈希,得到了對應(yīng)的index
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
// 判斷是否找到referent對應(yīng)的weak_entry_t
while (weak_table->weak_entries[index].referent != referent) {
// 如果發(fā)生碰撞收厨,則index依次+1晋柱,再次查找
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_table->weak_entries);// 異常,crash提示
hash_displacement++;
// 滿足沖撞次數(shù)诵叁,直接返回nil
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
// 返回取到的weak_entry_t
return &weak_table->weak_entries[index];
}
weak_register_no_lock注冊weak信息
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;
// weak變量地址
objc_object **referrer = (objc_object **)referrer_id;
// 判斷對象是否是TaggedPointer
if (!referent || referent->isTaggedPointer()) return referent_id;
bool deallocating; // deallocating 新對象是否正在釋放
if (!referent->ISA()->hasCustomRR()) {
// 沒有自定義retain/release的相關(guān)方法
deallocating = referent->rootIsDeallocating();
}
else {
// 有自定義retain/release的相關(guān)方法
BOOL (*allowsWeakReference)(objc_object *, SEL) =
(BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
SEL_allowsWeakReference);
if ((IMP)allowsWeakReference == _objc_msgForward) {
// 沒有現(xiàn)實allowsWeakReference允許弱引用 (NSObject類都是實現(xiàn)的)
return nil;
}
// 允許弱引用 deallocating = !YES雁竞;不允許弱引用deallocating = !NO
deallocating =
! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
}
// 如果對象正在釋放
if (deallocating) {
if (crashIfDeallocating) {
// crash提示
_objc_fatal("Cannot form weak reference to instance (%p) of "
"class %s. It is possible that this object was "
"over-released, or is in the process of deallocation.",
(void*)referent, object_getClassName((id)referent));
} else {
// 返回空
return nil;
}
}
// 這里才是主要代碼
weak_entry_t *entry; // 獲取對象對應(yīng)的weak_entry_t
if ((entry = weak_entry_for_referent(weak_table, referent))) {
// append_referrer把weak變量地址,加入weak_entry_t
// (1)先用數(shù)組inline_referrers存儲(count=4),滿了用referrers存儲
// (2)如果referrers到了3/4容量碑诉,就擴容2倍彪腔,重新存回去
// (3)如果referrers沒到3/4容量,直接存入referrers进栽,碰撞處理(哈希值++)
append_referrer(entry, referrer);
}
else {
// 新建一個weak_entry_t德挣,保存weak地址
weak_entry_t new_entry(referent, referrer);
// 如果weak_table滿容,進行自增長快毛,到了3/4就擴容2倍
weak_grow_maybe(weak_table);
// new_entry插入weak_table格嗅,碰撞處理(哈希值++)
weak_entry_insert(weak_table, &new_entry);
}
// 返回新對象
return referent_id;
}
weak_unregister_no_lock解除weak信息
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;
weak_entry_t *entry;
if (!referent) return;
// 獲取新對象對應(yīng)的weak_entry_t
if ((entry = weak_entry_for_referent(weak_table, referent))) {
// 移除weak地址
remove_referrer(entry, referrer);
// 判斷referrers是否為空
bool empty = true;
if (entry->out_of_line() && entry->num_refs != 0) {
// 判斷weak_entry_t 使用了referrers存儲weak指針且數(shù)量>0,說明還有weak指針
empty = false;
}
else {
// 使用內(nèi)聯(lián)數(shù)組inline_referrers存儲 (WEAK_INLINE_COUNT=4)
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}
if (empty) {
// 如果referrers為空唠帝,weak_table刪除entry
weak_entry_remove(weak_table, entry);
}
}
}
weak_clear_no_lock清除對象的weak_entry_t屯掖、設(shè)置referrers中所有的weak地址指向nil
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; // 總?cè)萘?
// 獲取weak地址數(shù)組,分為:inline_referrers與referrers
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) {
// weak地址全部設(shè)置為nil
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}
// weak_table移除entry
weak_entry_remove(weak_table, entry);
}
《Objective-C高級編程 iOS與OS X多線程和內(nèi)存管理》有一段觀點:
“使用附有__weak (NSLog(@"%@",obj1);)修飾符的變量襟衰,即使是使用注冊到autoreleasepool”
{
id tmp = objc_loadWeakretained(&obj1);
_objc_rootAutorelease(tmp);
NSLog(@"%@",tmp);
}
過時了懂扼,現(xiàn)在如下:
{
id temp = objc_loadWeakRetained(&weakObj);
NSLog(@"=====%@",temp);
objc_release(temp);
}
對象的釋放過程,在釋放過程的最后調(diào)用weak_clear_no_lock(&table.weak_table, (id)this);清除weak Hash表,并且所有的weak指針賦值nil
dealloc
- 調(diào)用 -release :isa.bits.extra_rc由0繼續(xù)減一時候觸發(fā)dealloc右蒲,
(1) 標記對象isa.deallocating = true,對象正在被銷毀赶熟,生命周期即將結(jié)束瑰妄,不能再有新的 __weak 弱引用.
(2) 調(diào)用 [self dealloc] (MRC需要在dealloc方法中手動釋放強引用的變量)
(3) 繼承關(guān)系中每一層的父類 都在調(diào)用 -dealloc,一直到根類(一般都是NSObject)- NSObject 調(diào) -dealloc
(1)只做一件事:調(diào)用 Objective-C runtime 中的 object_dispose() 方法- 調(diào)用 object_dispose()
(1) objc_destructInstance(obj);
(2 )free(obj);- objc_destructInstance(obj)執(zhí)行三個操作
(1) if (cxx) object_cxxDestruct(obj); // 釋放變量
1映砖、strong修飾的變量執(zhí)行objc_storeStrong(&ivar, nil)release對象间坐,ivar賦值nil,
2邑退、weak修飾的變量執(zhí)行objc_destroyWeak(&ivar) >> storeWeak(&ivar, nil ) >> weak_unregister_no_lock竹宋,將變量指向nil,且刪除變量對象的weak相關(guān)信息(referrers移除weak地址)地技。
(2) if (assoc) _object_remove_assocations(obj); // 移除Associate關(guān)聯(lián)數(shù)據(jù)(這就是不需要手動移除的原因)
(3) obj->clearDeallocating(); // 執(zhí)行weak_clear_no_lock蜈七,清空引用計數(shù)表、清空weak變量表且將所以引用指向nil