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的訪問情況,就是呼叫 objc_loadWeakRetained(id *location)
NSLog(@"=====%@",_weakObj);
// 編譯為下面程式碼
/*
id temp = objc_loadWeakRetained(&weakObj);
NSLog(@"=====%@",temp);
objc_release(temp);
*/
複製程式碼
#####NSObject.mm裡面的程式碼 ######objc_initWeak:定義並賦值一個weak變數
id
objc_initWeak(id *location, id newObj)
{
// 相比objc_storeWeak就是 多了這個判斷
if (!newObj) {
*location = nil;
return nil;
}
// 這裡傳遞了三個 bool 數值
// 使用 template 進行常量引數傳遞是為了優化效能
// 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)
{
// 函式取出附有__weak修飾符變數所引用的物件並retain
id obj = objc_loadWeakRetained(src);// 根據scr獲取指向的物件obj,retain obj
objc_initWeak(dst, obj); // 呼叫objc_initWeak 方法
objc_release(obj); // release obj
}
複製程式碼
######objc_destroyWeak:weak變數釋放
void
objc_destroyWeak(id *location)
{
(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
(location, nil);
}
複製程式碼
####主要就兩個方法storeWeak和objc_loadWeakRetained ####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相關的資訊(後面有簡單的說明)
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 應該與 oldObj 保持一致,如果不同,說明被其他執行緒所修改,goto retry
if (HaveOld && *location != oldObj) {
SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
goto retry;
}
// 防止弱引用間死鎖
// 並且通過 +initialize 初始化構造器保證所有弱引用的 isa 非空指向
if (HaveNew && newObj) {
// 獲得新物件的 isa 指標
Class cls = newObj->getIsa();
// 判斷 isa 非空且已經初始化
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized()) {
// 解鎖
SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
// 對其 isa 指標進行初始化
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
// 如果該類已經完成執行 +initialize 方法是最理想情況
// 如果該類 +initialize 線上程中
// 例如 +initialize 正在呼叫 storeWeak 方法
// 需要手動對其增加保護策略,並設定 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();
}
// 設定location指標指向newObj
*location = (id)newObj;
}
else {
// 沒有新值,則無需更改
}
SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
return (id)newObj;
}
複製程式碼
####objc_loadWeakRetained原始碼
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()) {
// 沒有自定義retain/release,呼叫系統的Retain
assert(cls->isInitialized());
if (! obj->rootTryRetain()) {
// 如果retain失敗,返回nil
result = nil;
}
}
else {
// 有自定義的retain/release
// 先看是否初始化initialize
if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
class_getMethodImplementation(cls, SEL_retainWeakReference);
// 是否實現了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;
}
複製程式碼
####SideTable、weak_table_t、weak_entry_t
struct SideTable {
spinlock_t slock; // 保證原子操作的自旋鎖
RefcountMap refcnts; // 引用計數的 hash 表
weak_table_t weak_table; // weak 引用全域性 hash 表
}
複製程式碼
struct weak_table_t {
weak_entry_t *weak_entries;// 雜湊表結構
size_t num_entries; // weak_entry_t個數
uintptr_t mask;// 總容量-1,用來弄雜湊值的 (n& mask)就是“除留取餘法”(n%總容量)
uintptr_t max_hash_displacement;// 碰撞次數
};
複製程式碼
typedef objc_object ** weak_referrer_t;
struct weak_entry_t {
DisguisedPtrobjc_object> referent;
union {
struct {
weak_referrer_t *referrers; // 雜湊表結構
uintptr_t out_of_line : 1; // 1是referrers儲存,0是inline_referrers
uintptr_t num_refs : PTR_MINUS_1;// 已存referrer總數
uintptr_t mask; // 同上
uintptr_t max_hash_displacement; // 同上
};
struct {
// 如果weak變數少,用陣列來存放,如果大於4個,就使用referrers
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
}
}
複製程式碼
上面三層結構下來,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;
// 物件地址雜湊,得到了對應的index
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
// 判斷是否找到referent對應的weak_entry_t
while (weak_table->weak_entries[index].referent != referent) {
// 如果發生碰撞,則index依次+1,再次查詢
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_table->weak_entries);// 異常,crash提示
hash_displacement++;
// 滿足衝撞次數,直接返回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;
if (!referent || referent->isTaggedPointer()) return referent_id;
// deallocating 物件是否正在釋放
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
// 沒有自定義retain/release的
deallocating = referent->rootIsDeallocating();
}
else {
// 有自定義retain/release的
BOOL (*allowsWeakReference)(objc_object *, SEL) =
(BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
SEL_allowsWeakReference);
if ((IMP)allowsWeakReference == _objc_msgForward) {
// 沒有現實allowsWeakReference允許弱引用
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; // 獲取物件對應的weak_entry_t
if ((entry = weak_entry_for_referent(weak_table, referent))) {
// 把weak變數地址,加入weak_entry_t
// 先用陣列inline_referrers儲存,滿了用雜湊表referrers
// 如果referrers到了3/4容量,就擴容2倍,重新存回去
// 沒滿,直接存入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;
// 獲取物件對應的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) {
empty = false;
}
else {
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、設定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) {
// objc原始碼測試,全在這裡return了,後面一坨程式碼沒有走。不知道什麼問題?
return;
}
weak_referrer_t *referrers;
size_t count; // 總容量
// 獲取weak地址陣列,分為: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地址全部設定為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多執行緒和記憶體管理》有一段觀點:
“使用附有__weak (NSLog(@"%@",obj1);)修飾符的變數,即使是使用註冊到autoreleasepool” { id tmp = objc_loadWeakretained(&obj1); _objc_rootAutorelease(tmp); NSLog(@"%@",tmp); }
過時了,現在如下: { id temp = objc_loadWeakRetained(&weakObj); NSLog(@"=====%@",temp); objc_release(temp); }
######物件的釋放過程,在釋放過程的最後呼叫weak_clear_no_lock(&table.weak_table, (id)this);清除weak Hash表,並且所有的weak指標賦值nil
###dealloc
1. 呼叫 -release :isa.bits.extra_rc由0繼續減一時候觸發dealloc,
* 標記物件isa.deallocating = true,物件正在被銷燬,生命週期即將結束.
* 不能再有新的 __weak 弱引用.
* 呼叫 [self dealloc] (MRC需要在dealloc方法中手動釋放強引用的變數)
* 繼承關係中每一層的父類 都在呼叫 -dealloc,一直到根類(一般都是NSObject)
2. NSObject 調 -dealloc
* 只做一件事:呼叫 Objective-C runtime 中的 object_dispose() 方法
3. 呼叫 object_dispose()
(1)objc_destructInstance(obj);
(2)free(obj);
4. objc_destructInstance(obj)執行三個操作
1、if (cxx) object_cxxDestruct(obj); // 釋放變數
(1) strong的objc_storeStrong(&ivar, nil)release物件,ivar賦值nil,
(2) weak ivar,出了作用域,objc_destroyWeak(&ivar) >> storeWeak(&ivar, nil) 將ivar指向nil且ivar的地址從物件的weak表中刪除。
2、if (assoc) _object_remove_assocations(obj); // 移除Associate關聯資料(這就是不需要手動移除的原因)
3、obj->clearDeallocating(); // 清空引用計數表、清空weak變數表且將所以引用指向nil
複製程式碼
主要參考: weak 弱引用的實現方式 ObjC Runtime 中 Weak 屬性的實現 (上) ObjC Runtime 中 Weak 屬性的實現 (中)