這個問題的答案網(wǎng)上有很多,現(xiàn)在先給出答案逆日。但本文的重點是從底層源碼的角度來分析鸵赫,尤其是對weak的整個源碼過程進行分析抵蚊。
1航邢、 結(jié)論
1.1 區(qū)別
1.1.1 修飾變量類型的區(qū)別
weak 只可以修飾對象集币。如果修飾基本數(shù)據(jù)類型,編譯器會報錯-“Property with ‘weak’ attribute must be of object type”翠忠。
assign 可修飾對象鞠苟,和基本數(shù)據(jù)類型。當需要修飾對象類型時秽之,MRC時代使用unsafe_unretained当娱。當然,unsafe_unretained也可能產(chǎn)生野指針考榨,所以它名字是"unsafe_”跨细。
1.1.2.是否產(chǎn)生野指針的區(qū)別
weak 不會產(chǎn)生野指針問題。因為weak修飾的對象釋放后(引用計數(shù)器值為0)河质,指針會自動被置nil冀惭,之后再向該對象發(fā)消息也不會崩潰。 weak是安全的掀鹅。
assign 如果修飾對象散休,會產(chǎn)生野指針問題;如果修飾基本數(shù)據(jù)類型則是安全的乐尊。修飾的對象釋放后戚丸,指針不會自動被置空,此時向?qū)ο蟀l(fā)消息會崩潰扔嵌。
1.2 相似
都可以修飾對象類型限府,但是assign修飾對象會存在問題。
代碼使用weak后clang編譯后報錯痢缎,需要使用下方的命令
clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations main.m
2胁勺、assign原理
@property(nonatomic, assign)int age;
- 通過clang編譯之后查看set、get方法独旷,發(fā)現(xiàn)本質(zhì)就是
通過地址操作直接對內(nèi)存進行操作
署穗。這一點和strong
很像,不同的是strong標記的是對象势告,是存在引用計數(shù)的
; - assign修飾的變量
不涉及到引用計數(shù)蛇捌,所以在修飾對象時不安全
;
3抚恒、weak 原理
id __weak weakObj = obj;
//clang之后
id __attribute__((objc_ownership(weak))) weakObj = obj;
只有在llvm庫中找到了objc_ownership
咱台,但是沒有更多進展了,所以還是從libobjc
著手俭驮。在代碼中增加斷點并打開匯編調(diào)試
回溺。
- 看到對象轉(zhuǎn)weak之后是調(diào)用了
objc_initWeak
.
3.1 涉及到的數(shù)據(jù)結(jié)構(gòu)
在此之前先了解一下在weak操作中會涉及到的數(shù)據(jù)結(jié)構(gòu):StripedMap
春贸、SideTable
、weak_table_t
遗遵、weak_entry_t
萍恕,方便后續(xù)的理解.
3.1.1 StripedMap
//StripedMap通過靜態(tài)變量SideTablesMap進行地址的獲取,地址唯一的3狄T试痢!翼岁!
static objc::ExplicitInit<StripedMap<SideTable>> SideTablesMap;
static StripedMap<SideTable>& SideTables() {
return SideTablesMap.get();
}
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 }; //真機8個
#else
enum { StripeCount = 64 }; //模擬器64個
#endif
struct PaddedT {
//字節(jié)對齊:64字節(jié)
//數(shù)組中每一個元素的大小都是64的倍數(shù),因為SideTable占64字節(jié)
T value alignas(CacheLineSize);
};
//該類的數(shù)據(jù)存儲方法:array类垫。并且在不同的設(shè)備下不同
PaddedT array[StripeCount];
//自定義操作符[]
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}
//通過位運算得出一個不超過當前數(shù)組上限的index
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}
//還有些鎖的操作
}
- 通過
StripedMap(hash表)
封裝后完成了內(nèi)存字節(jié)對齊
、[]的自定義
琅坡、還有一些鎖的操作
悉患; -
<SideTable>
通過泛型定義:StripedMap
中就是存儲n(iPhone設(shè)備8、其他64)個SideTable
; -
唯一的StripedMap一對多SideTable
; - 將要轉(zhuǎn)換的
對象
地址通過位運算處理后放入某個sidetable中
,并不是所有對象都存在一個sidetable
,提升了查詢效率
;
3.1.2 SideTable(抽象對象)
struct SideTable {
spinlock_t slock;
//引用計數(shù)的散列表
RefcountMap refcnts;
//存儲數(shù)據(jù)
weak_table_t weak_table;
//構(gòu)造函數(shù)
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
//析構(gòu)函數(shù)
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
//主要是一些鎖操作
};
- 和StripedMap一樣榆俺,通過封裝完成了一些
鎖方法的添加
,而且不用直接暴露weak_table
增加穩(wěn)定性售躁; -
SideTable是hash表的載體
,通過SideTable完成了weak_table的操作茴晋; -
唯一的StripedMap一對多SideTable
-SideTable一對一weak_table
;
3.1.3 weak_table_t (hash表)
struct weak_table_t {
weak_entry_t *weak_entries; //存儲數(shù)據(jù)
size_t num_entries; // 存儲數(shù)據(jù)的總數(shù)
uintptr_t mask;
uintptr_t max_hash_displacement;
};
-
唯一的StripedMap一對多SideTable
-SideTable一對一weak_table
-weak_table一對多weak_entries
;
3.1.4 weak_entry_t(存儲對象)
#define WEAK_INLINE_COUNT 4
#define REFERRERS_OUT_OF_LINE 2
struct weak_entry_t {
// 相當于hash表中的key陪捷,存儲是被weak的目標對象地址
DisguisedPtr<objc_object> referent;
union {
struct {
// 轉(zhuǎn)換后對象的指針地址
// 由于存在多次轉(zhuǎn)換,該結(jié)構(gòu)可以看做數(shù)組結(jié)構(gòu)
weak_referrer_t *referrers; //8字節(jié)
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2; // 存儲數(shù)據(jù)的總數(shù)
uintptr_t mask;
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]; //8字節(jié)
};
};
bool out_of_line() {
return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
}
};
-
唯一的StripedMap一對多SideTable
-SideTable一對一weak_table
-weak_table一對多weak_entries
-weak_entries一對多referrers/inline_referrers
; - 在這里巧妙的使用了一個聯(lián)合體诺擅。
- 如果一個對象對應(yīng)的弱引用數(shù)目較少的話(<=4)揩局,則其弱引用會被依次保存到一個
inline_referrers數(shù)組結(jié)構(gòu)
里。 - 如果弱引用多(>4)后掀虎,則通過指針平移存儲在
referrers
里凌盯,并開始記錄總數(shù)。會在何時的時機進行擴容烹玉。
- 如果一個對象對應(yīng)的弱引用數(shù)目較少的話(<=4)揩局,則其弱引用會被依次保存到一個
- 聯(lián)合體的內(nèi)存是共用的驰怎。默認聯(lián)合體創(chuàng)建后的第二個8字節(jié)位置為0,也就是out_of_line_ness = 0二打。所以默認會放入inline_referrers這個結(jié)構(gòu)中县忌。
結(jié)構(gòu)圖
3.2 storeWeak具體操作
// location是需要weak的對象
// newObjz轉(zhuǎn)換后的對象
static id storeWeak(id *location, objc_object *newObj) {
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
retry:
//通過haveOld、haveNew完成新增和刪除操作
//haveOld是刪除操作
//haveNew是添加操作
if (haveOld) {
// location是weak_entry_t結(jié)構(gòu)继效,取地址后首地址對應(yīng)的是referent
// oldObj = 目標對象-表中的key
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else { oldTable = nil; }
if (haveNew) {
newTable = &SideTables()[newObj];
} else { newTable = nil; }
//線程安全
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
// 刪除操作
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 ? CrashIfDeallocating : ReturnNilIfDeallocating);
*location = (id)newObj;
}
//解鎖
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
-
&SideTables()[oldObj];
:從StripedMap
找到目標hash表sidetable
症杏;
3.2.1 oldObj = *location解釋
- oldObj就是referent,也就是
hash表中的key
;
3.2.2 weak_register_no_lock新增
id weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id,..)
{
// hash表中的key
objc_object *referent = (objc_object *)referent_id;
// 要插入的目標對象
objc_object **referrer = (objc_object **)referrer_id;
//小對象不處理
if (referent->isTaggedPointerOrNil()) return referent_id;
//省略了一些錯誤處理
weak_entry_t *entry;
// 通過referent從hash表中找到weak_entry_t
if ((entry = weak_entry_for_referent(weak_table, referent))) {
//如果找到就進行添加
append_referrer(entry, referrer);
}
else {
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
//如果沒找到就進行插入
weak_entry_insert(weak_table, &new_entry);
}
return referent_id;
}
-
weak_entry_for_referent
通過while循環(huán)完成referent
的匹配瑞信,篇幅原因代碼就不放出了厉颤; - 這里出現(xiàn)了
append_referrer
和weak_entry_insert
兩個方法。weak_entry_insert
相對簡單凡简,本次注重分析append_referrer
逼友;
3.2.3 append_referrer
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
//out_of_line這個方法在上面放出過精肃,判斷是否是線性結(jié)構(gòu)儲存(referrers)
if (! entry->out_of_line()) {
// Try to insert inline.
// 如果在4個以內(nèi)有空值
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return;
}
}
//如果4個放滿了,放棄inline_referrers存儲帜乞,初始化referrers結(jié)構(gòu)
weak_referrer_t *new_referrers = (weak_referrer_t *)
calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
//通過巡皇的方式完成inline_referrers數(shù)組中所有值的復(fù)制
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
new_referrers[i] = entry->inline_referrers[i];
}
// 狀態(tài)值的設(shè)置司抱,尤其主要out_of_line_ness設(shè)置為2,該判斷就不會在進入
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;
}
// 是否設(shè)置線性成功
ASSERT(entry->out_of_line());
// 如果超過3/4就進行擴容
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;
//找到空的位置后完成賦值
while (entry->referrers[index] != nil) {
hash_displacement++;
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
}
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
}
- 就是一些
狀態(tài)參數(shù)
黎烈、指針的操作
4习柠、 weak的使用
轉(zhuǎn)成weak之后完成調(diào)用
NSObject *obj1 = [HRTest alloc];
HRTest __weak *weakObj1 = obj1;
[weakObj1 say1];
-
在調(diào)用位置打下斷點,并且開啟匯編
- 將obj轉(zhuǎn)為weak照棋;
- 在調(diào)用say1之前津畸,先調(diào)用了
objc_loadWeakRetained
方法; - 通過objc_msgSend完成say1消息發(fā)送;
4.1 weak之后地址情況
- 即使轉(zhuǎn)weak之后對象的指針地址是
不會變化
的,而且變量weakObjc1
的也是指向objc1
必怜。相當于一次淺拷貝肉拓,但不涉及引用計數(shù)的變化。
4.2 objc_loadWeakRetained
id objc_loadWeakRetained(id *location) {
id obj;
id result;
Class cls;
SideTable *table;
retry:
//和storeWeak中oldvalue邏輯一樣梳庆,找到key和value
obj = *location;
if (obj->isTaggedPointerOrNil()) return obj;
//找到對應(yīng)的sidetable表
table = &SideTables()[obj];
table->lock();
if (*location != obj) {
table->unlock();
goto retry;
}
result = obj;
cls = obj->ISA();
if (! cls->hasCustomRR()) {
ASSERT(cls->isInitialized());
if (! obj->rootTryRetain()) {
result = nil;
}
}
else {
if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
lookUpImpOrForwardTryCache(obj, @selector(retainWeakReference), cls);
if ((IMP)tryRetain == _objc_msgForward) {
result = nil;
}
else if (! (*tryRetain)(obj, @selector(retainWeakReference))) {
result = nil;
}
}
else {
table->unlock();
class_initialize(cls, obj);
goto retry;
}
}
table->unlock();
return result;
}
- 看下來就是發(fā)現(xiàn)暖途,該方法就是
在方法調(diào)用前檢查weak對象是否已經(jīng)被置nil
;
3膏执、 銷毀
由于weak引用
之后不會增加對象的引用計數(shù)驻售,所以在對象銷毀的時候,weak是如何銷毀呢?
// 直接放出和weak銷毀有關(guān)的步驟
inline void objc_object::clearDeallocating() {
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
...
}
void objc_object::sidetable_clearDeallocating() {
SideTable& table = SideTables()[this];
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
table.refcnts.erase(it);
}
table.unlock();
}
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
// remove entry
if (entry->out_of_line()) free(entry->referrers);
bzero(entry, sizeof(*entry));
weak_table->num_entries--;
//如果表格大部分是空的更米,則進行縮容欺栗。
weak_compact_maybe(weak_table);
}
- 釋放weak_entry_t對象;
- 并且把side_table中的對象進行clean征峦;