weak
是弱引用党涕,用weak
描述修飾或者所引用對象的計(jì)數(shù)器不會加一瘦真,并且會在引用的對象被釋放的時候自動被設(shè)置為nil
,大大避免了野指針訪問壞內(nèi)存引起崩潰的情況眠寿,它主要用于解決循環(huán)引用型酥。
NSObject *obj = [[NSObject alloc] init];
__weak id obj1 = obj;
obj1 = @"123";
...
0000000100000efd call imp___stubs__objc_initWeak
0000000100000f02 lea rdi, qword [rbp+var_20] ; argument "addr" for method imp___stubs__objc_storeWeak
0000000100000f06 lea rsi, qword [cfstring_123] ; @"123", argument "value" for method imp___stubs__objc_storeWeak
0000000100000f0d mov qword [rbp+var_28], rax
0000000100000f11 call imp___stubs__objc_storeWeak
0000000100000f16 lea rdi, qword [rbp+var_20] ; argument "instance" for method imp___stubs__objc_destroyWeak
0000000100000f1a mov dword [rbp+var_4], 0x0
0000000100000f21 mov qword [rbp+var_30], rax
0000000100000f25 call imp___stubs__objc_destroyWeak
...
從上面代碼可以看出創(chuàng)建weak
是通過objc_initWeak
方法山憨,賦值是通過objc_storeWeak
,跟屬性用weak
關(guān)鍵字調(diào)用的同一個方法弥喉,銷毀是通過objc_destroyWeak
方法郁竟。
id objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
id objc_storeWeak(id *location, id newObj)
{
return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object *)newObj);
}
從上面可以看出weak
的核心都是調(diào)用storeWeak
方法,區(qū)別是模板的幾個參數(shù)由境,創(chuàng)建是沒有舊值得枪孩,之后賦值都是有舊值得。
template <HaveOld haveOld, HaveNew haveNew,
CrashIfDeallocating crashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj)
{
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
retry:
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
//舊值改變了,就再走一遍
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
if (haveNew && newObj) {
Class cls = newObj->getIsa();
// 判斷 isa 非空且已經(jīng)初始化
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
previouslyInitializedClass = cls;
goto retry;
}
}
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);
// 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.
}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
這個函數(shù)首先引入了第一個結(jié)構(gòu)SideTable
蔑舞,還可以通過SideTables
得到這個結(jié)構(gòu)的對象,看下他們的結(jié)構(gòu)嘹屯。
alignas(StripedMap<SideTable>) static uint8_t
SideTableBuf[sizeof(StripedMap<SideTable>)];
static void SideTableInit() {
new (SideTableBuf) StripedMap<SideTable>();
}
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
SideTableInit
這個方法在程序開始運(yùn)行的時候都調(diào)用了攻询,它初始化了StripedMap
,而且SideTables()
獲取到就是這個州弟,每次獲取都是最新值钧栖。
template<typename T>
class StripedMap {
enum { CacheLineSize = 64 };
#if TARGET_OS_EMBEDDED
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
struct PaddedT {
T value alignas(CacheLineSize);
};
PaddedT array[StripeCount];
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
結(jié)構(gòu)就有一個成員,PaddedT array[StripeCount]
婆翔,從這可以看出這個數(shù)組有64個PaddedT
結(jié)構(gòu)的變量拯杠,每個PaddedT
結(jié)構(gòu)占64個字節(jié),也就是說StripedMap
結(jié)構(gòu)大小就是4096個字節(jié)啃奴。PaddedT
在這里就是SideTable
潭陪。通過indexForPointer
方法可以看到,分配SideTable
的時候最蕾,是通過對象的地址的((addr >> 4) ^ (addr >> 9)) % StripeCount
運(yùn)算得到的依溯,因?yàn)樽詈笫亲?4的模運(yùn)算,所以結(jié)果只能是從0-63瘟则。
using spinlock_t = mutex_tt<LOCKDEBUG>;
template <bool Debug>
class mutex_tt : nocopy_t {
os_unfair_lock mLock;
...
struct SideTable {
spinlock_t slock;//本質(zhì)就是os_unfair_lock鎖
RefcountMap refcnts;//weak這里暫時用不上 arc會用到
weak_table_t weak_table;//weak表
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }
// Address-ordered lock discipline for a pair of side tables.
template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
當(dāng)StripedMap
初始化成員array
的時候黎炉,就初始化了64個SideTable
,每個SideTable
初始化都會把weak_table
空間置為0醋拧。從上面可以看出來每一個弱引用都是一個weak_entry_t
對象慷嗜。舉個很形象的例子,有一個宿舍樓(SideTables
)丹壕,他有64個宿舍(SideTable
)庆械,宿舍有很多個床位(weak_entry_t
),它會根據(jù)里面人的多少來進(jìn)行適當(dāng)?shù)財(cái)U(kuò)容雀费,而且每個宿舍都有一把鎖(spinlock_t
)來保護(hù)財(cái)產(chǎn)安全干奢,當(dāng)一個進(jìn)去時就鎖上,出來時解鎖盏袄。至于怎么分配宿舍忿峻,是根據(jù)人的編號來行進(jìn)的,在這里就是根據(jù)對象的地址辕羽,再根據(jù)上面的indexForPointer
方法來進(jìn)行分配逛尚。
#define WEAK_INLINE_COUNT 4
#define REFERRERS_OUT_OF_LINE 2
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
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];
};
};
bool out_of_line() {
return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
}
weak_entry_t& operator=(const weak_entry_t& other) {
memcpy(this, &other, sizeof(other));
return *this;
}
weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
: referent(newReferent)
{
inline_referrers[0] = newReferrer;
for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
inline_referrers[i] = nil;
}
}
};
經(jīng)過后面的一些代碼的驗(yàn)證,可以看出referent
它存放的是weak
要引用的對象刁愿,而inline_referrers
存放的是weak
對象本身绰寞,初始化為4個,如果有多于4的時候,會進(jìn)行擴(kuò)容滤钱,也就是有多個weak
指向同一個對象觉壶。至于DisguisedPtr<objc_object>
,你可以把它當(dāng)成id
件缸。有了這些铜靶,看后面的代碼會清晰很多。
現(xiàn)在回過頭看storeWeak
函數(shù)他炊,haveOld
為真的時候争剿,大部分情況是weak
指針指向新值了,還有一種情況是weak
屬性的時候痊末。weak_unregister_no_lock
函數(shù)是清理舊值的蚕苇,現(xiàn)在先看注冊新值的情況,是這個weak_register_no_lock
函數(shù)凿叠。
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;
if (!referent || referent->isTaggedPointer()) return referent_id;
// 保證引用對象是否有效
bool deallocating;
// 是否有自定義的默認(rèn)方法涩笤,如retain/release
if (!referent->ISA()->hasCustomRR()) {
//是否釋放
deallocating = referent->rootIsDeallocating();
}
else {
BOOL (*allowsWeakReference)(objc_object *, SEL) =
(BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
SEL_allowsWeakReference);
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
}
if (deallocating) {
if (crashIfDeallocating) {
_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;
}
}
// now remember it and where it is being stored
weak_entry_t *entry;
//獲取weak引用對象
if ((entry = weak_entry_for_referent(weak_table, referent))) {
//如果存在就把weak對象 加進(jìn)去
append_referrer(entry, referrer);
}
else {
//創(chuàng)建weak弱引用表關(guān)聯(lián)
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
return referent_id;
}
先一行一行分析創(chuàng)建weak
弱引用:
weak_entry_t new_entry(referent, referrer);
//對應(yīng)于
weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
: referent(newReferent)
{
inline_referrers[0] = newReferrer;
for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
inline_referrers[i] = nil;
}
}
從這就能看出referent
對應(yīng)的是weak
要引用的對象,inline_referrers
對應(yīng)的是weak
本身幔嫂。
static void weak_grow_maybe(weak_table_t *weak_table)
{
size_t old_size = TABLE_SIZE(weak_table);
// Grow if at least 3/4 full.
if (weak_table->num_entries >= old_size * 3 / 4) {
weak_resize(weak_table, old_size ? old_size*2 : 64);
}
}
static void weak_resize(weak_table_t *weak_table, size_t new_size)
{
size_t old_size = TABLE_SIZE(weak_table);
weak_entry_t *old_entries = weak_table->weak_entries;
weak_entry_t *new_entries = (weak_entry_t *)
calloc(new_size, sizeof(weak_entry_t));
weak_table->mask = new_size - 1;
weak_table->weak_entries = new_entries;
weak_table->max_hash_displacement = 0;
weak_table->num_entries = 0; // restored by weak_entry_insert below
if (old_entries) {
weak_entry_t *entry;
weak_entry_t *end = old_entries + old_size;
for (entry = old_entries; entry < end; entry++) {
if (entry->referent) {
weak_entry_insert(weak_table, entry);
}
}
free(old_entries);
}
}
從一開始old_size ? old_size*2 : 64
為0辆它,所以開辟出了64個大小為sizeof(weak_entry_t)
空間,如果里面的個數(shù)超過64的3/4了履恩,就開始再次擴(kuò)容锰茉,并把之前的表結(jié)構(gòu)再一個一個插入進(jìn)去,weak_entry_insert(weak_table, entry);
切心。
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
weak_entry_t *weak_entries = weak_table->weak_entries;
assert(weak_entries != nil);
size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (weak_entries[index].referent != nil) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_entries);
hash_displacement++;
}
weak_entries[index] = *new_entry;
weak_table->num_entries++;
if (hash_displacement > weak_table->max_hash_displacement) {
weak_table->max_hash_displacement = hash_displacement;
}
}
hash_pointer
是根據(jù)對象的地址做一次hash
運(yùn)算飒筑,用的是ptr_hash
方法,再跟總共的空間做與運(yùn)行绽昏,也就是跟63做與運(yùn)算(如果再次擴(kuò)容就不是63了)协屡。如果當(dāng)前位置有值了,就再通過上面算法再換一個全谤,接下來就是簡單賦值了肤晓。
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];
}
通過一個對象獲取對應(yīng)的weak_entry_t
,這里跟插入的差不多认然,都是根據(jù)地址來進(jìn)行操作补憾。
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
if (! entry->out_of_line()) {
// Try to insert inline.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return;
}
}
// Couldn't insert inline. Allocate out of line.
weak_referrer_t *new_referrers = (weak_referrer_t *)
calloc(WEAK_INLINE_COUNT, sizeof(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];
}
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;
while (entry->referrers[index] != nil) {
hash_displacement++;
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
}
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++;
}
從上面可知,有2種模式卷员,如果有小于等于4個weak
的話盈匾,就用inline_referrers
本身,就如最開始for
循環(huán)毕骡,如果大于4個的話削饵,就跟weak
表結(jié)構(gòu)一樣了岩瘦,用referrers
進(jìn)行存儲了,如果里面?zhèn)€數(shù)大于3/4就自動擴(kuò)容一倍窿撬,它用的是w_hash_pointer
方法對地址進(jìn)行hash
取值启昧,其實(shí)跟hash_pointer
一樣,調(diào)用的都是同一個方法ptr_hash
劈伴。
void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
weak_entry_t *entry;
if (!referent) return;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
remove_referrer(entry, referrer);
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) {
weak_entry_remove(weak_table, entry);
}
}
}
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
if (! entry->out_of_line()) {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == old_referrer) {
entry->inline_referrers[i] = nil;
return;
}
}
...
}
size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
....
entry->referrers[index] = nil;
entry->num_refs--;
}
移除weak
引用箫津,主要就是把相應(yīng)位置的指針置為空,entry->inline_referrers[i] = nil;
宰啦、entry->referrers[index] = nil;
。如果沒有引用了饼拍,就直接調(diào)用weak_entry_remove
方法把weak
表里面的相應(yīng)引用給置為空赡模。
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);
}
當(dāng)一個對象銷毀的時候,如果有弱引用师抄,會調(diào)用weak_clear_no_lock
方法進(jìn)行清除漓柑,最后也會調(diào)用weak_entry_remove
。
inline void objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
}
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);
}
table.unlock();
}
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);
}
這里的邏輯跟之前相似叨吮,看上面的即可辆布。weak
的介紹到此為止,詳細(xì)的可以自己運(yùn)行別人編譯好的runtime
自己進(jìn)行調(diào)式茶鉴。