上一篇: iOS底層原理04 - 類的結(jié)構(gòu)
在Student類中添加分別由strong、copy、weak修飾的屬性:

在通過(guò)Clang編譯后的.cpp文件中,看到會(huì)生成三個(gè)對(duì)應(yīng)的setter方法:

唯獨(dú)在setAge的方法調(diào)用中,是通過(guò)objc_setProperty方法實(shí)現(xiàn)的。
1. copy
我們?cè)賝bjc-818.4源碼中搜索,看到了如下幾種方法:

- setProperty
在objc源碼中,搜索不到關(guān)于setProperty相關(guān)的調(diào)用,那么針對(duì)不同的修飾符,是如何跳轉(zhuǎn)到對(duì)應(yīng)的setProperty方法的呢?我們打開(kāi)llvm源碼來(lái)查找:

在llvm層,其實(shí)對(duì)copy屬性做了編譯器優(yōu)化,之后才會(huì)調(diào)用objc中的objc_setProperty_xxx方法。我們這里定義的age屬性使用了nonatomic和copy修飾,故name = "objc_setProperty_nonatomic_copy".

最后進(jìn)入reallySetProperty流程,這里可以通過(guò)斷點(diǎn)調(diào)試來(lái)看:

- reallySetProperty
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
- 如果是
copy或者mutableCopy并不會(huì)對(duì)新值進(jìn)行objc_retain操作,而是copyWithZone,返回一個(gè)不可變的對(duì)象 - 若為
atomic原子性的屬性,會(huì)在setter方法添加spinlock_t類型的自旋鎖來(lái)保證多線程寫入安全。
2. weak
從.cpp中看到,使用weak修飾的對(duì)象,并不會(huì)走上面的objc_setProperty方法,而是走objc源碼中的objc_storeWeak來(lái)更新弱引用指針的指向

static id
storeWeak(id *location, objc_object *newObj)
{
ASSERT(haveOld || haveNew);
if (!haveNew) ASSERT(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
// 用地址作為唯一標(biāo)識(shí)來(lái)獲取新值和舊值鎖的位置
// 通過(guò)地址來(lái)創(chuàng)建索引標(biāo)志,防止桶的重復(fù)
retry:
if (haveOld) {
oldObj = *location;
// 獲得以oldObj為索引存儲(chǔ)的SideTable
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
// 獲得以newObj為索引存儲(chǔ)的SideTable
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
// 加鎖操作,防止多線程中競(jìng)爭(zhēng)沖突
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
// 避免線程沖突重處理
// location 應(yīng)該與 oldObj 保持一致,如果不同說(shuō)明當(dāng)前的 location 已經(jīng)處理過(guò) oldObj 可是又被其他線程所修改
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
// 避免弱引用機(jī)制和 +initialize 之間的死鎖
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
class_initialize(cls, (id)newObj);
previouslyInitializedClass = cls;
goto retry;
}
}
// 清除舊值
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// 設(shè)置新值
if (haveNew) {
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
if (!newObj->isTaggedPointerOrNil()) {
// 弱引用位初始化操作
// 引用計(jì)數(shù)那張散列表的weak引用對(duì)象的引用計(jì)數(shù)中標(biāo)識(shí)為weak引用
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.
}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
callSetWeaklyReferenced((id)newObj);
return (id)newObj;
}
-
location是指向weak指針的指針,因?yàn)橐薷膚eak指針 - 上面
storeWeak的過(guò)程,就是在解除與舊對(duì)象的關(guān)系,并與新對(duì)象建立聯(lián)系
runtime維護(hù)了一張weak表,用來(lái)存儲(chǔ)指向某個(gè)對(duì)象的所有weak指針,從源碼中看到,這張表其實(shí)就是SideTables,這是一張hash表,key是對(duì)象的地址,value是weak指針的數(shù)組。
SideTables 散列表

使用對(duì)象來(lái)獲取對(duì)應(yīng)的
SideTable:&SideTables()[newObj]spinlock_t slock: 自旋鎖,用于上鎖/解鎖 SideTableRefcountMap refcnts:以DisguisedPtr<objc_object>為key的hash表,用來(lái)存儲(chǔ)OC對(duì)象的引用計(jì)數(shù)(僅在未開(kāi)啟isa優(yōu)化 或 在isa優(yōu)化情況下isa_t的引用計(jì)數(shù)溢出時(shí)才會(huì)用到)。
// RefcountMap disguises its pointers because we
// don't want the table to act as a root for `leaks`.
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,RefcountMapValuePurgeable> RefcountMap;
RefcountMap是以DenseMap為模板創(chuàng)建的,三個(gè)參數(shù)分別為hash key類型,value類型,以及是否要在value==0時(shí)自動(dòng)釋放掉響應(yīng)的hash節(jié)點(diǎn),這里是true。
- weak_table_t weak_table : 存儲(chǔ)對(duì)象弱引用指針的hash表。是OC weak功能實(shí)現(xiàn)的核心數(shù)據(jù)結(jié)構(gòu)。
struct weak_table_t {
weak_entry_t *weak_entries; // hash數(shù)組 存儲(chǔ)弱引用對(duì)象的相關(guān)信息
size_t num_entries; // hash數(shù)組的元素個(gè)數(shù)
uintptr_t mask; // hash數(shù)組長(zhǎng)度-1,參與hash計(jì)算
uintptr_t max_hash_displacement; // 可能會(huì)發(fā)生的hash沖突的最大次數(shù),用于判斷是否出現(xiàn)了邏輯錯(cuò)誤
};
weak_table_t是全局的弱引用表,將對(duì)象id存儲(chǔ)為鍵,將weak_entry_t存儲(chǔ)為它們的值。
在我們的App中,多個(gè)對(duì)象會(huì)重用同一個(gè)SideTable節(jié)點(diǎn),也就是說(shuō),weak_table會(huì)存儲(chǔ)多個(gè)對(duì)象的弱引用信息。因此在一個(gè)SideTable中,又會(huì)通過(guò)weak_table作為hash表再次分散存儲(chǔ)每一個(gè)對(duì)象的弱引用信息。
- weak_entry_t
struct weak_entry_t {
DisguisedPtr<objc_object> referent; // 被弱引用的對(duì)象
// 兩種形式的聯(lián)合體 - 動(dòng)態(tài)數(shù)組weak_referrer_t和定長(zhǎng)數(shù)組weak_referrer_t
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
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];
};
};
...
};
-
DisguisedPtr<objc_object> referent:弱引用對(duì)象的指針摘要 -
union:聯(lián)合體,有兩種形式 - 動(dòng)態(tài)數(shù)組weak_referrer_t *referrers和定長(zhǎng)數(shù)組weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]。 用來(lái)存儲(chǔ)弱引用該對(duì)象的指針的指針。當(dāng)弱引用該對(duì)象的指針數(shù)目小于等于WEAK_INLINE_COUNT 4時(shí),為定長(zhǎng)數(shù)組;當(dāng)超過(guò)時(shí),會(huì)將定長(zhǎng)數(shù)組中的元素轉(zhuǎn)移到動(dòng)態(tài)數(shù)組中。
clearDeallocating
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this); // 從引用計(jì)數(shù)表中擦除該對(duì)象的引用計(jì)數(shù)
}
table.unlock();
}
當(dāng)一個(gè)對(duì)象被釋放,調(diào)用clearDeallocating后會(huì)做如下操作:
- 由當(dāng)前對(duì)象作為key找到
SideTable - 將當(dāng)前對(duì)象轉(zhuǎn)為
objc_object類型并作為key,從SideTable的weak_table_t中取出weak_entry_t - 遍歷
weak_referrer_t,將指向該對(duì)象的弱引用指針置為nil; - 將
referrers數(shù)組移除,weak_table中hash數(shù)組的元素個(gè)數(shù)-1 -
table.refcnts.eraser()從引用計(jì)數(shù)表中擦除該對(duì)象的引用計(jì)數(shù)
3. strong

若以strong修飾的屬性,會(huì)調(diào)用objc_storeStrong方法,進(jìn)行新值retain和舊值release