【原創(chuàng)博文,轉(zhuǎn)載請(qǐng)注明出處!】
寫在最前面:你們別光看啊,發(fā)現(xiàn)我說的不對(duì)的地方請(qǐng)指出或留言,希望我們一起進(jìn)步。
前幾天在iOS圈內(nèi)流傳著“一個(gè)關(guān)于歷年來weak的面試題答案”的段子,感覺有點(diǎn)搞怪O(∩_∩)O~~。是的,做技術(shù)開發(fā)門檻越來越高了。。。
結(jié)合objc源碼,我寫了個(gè)簡(jiǎn)單測(cè)試demo,關(guān)于對(duì)象的三個(gè)修飾詞__strong
、__weak
、__unsafe_unretained
,測(cè)試結(jié)果分別用“01__strong指針引用對(duì)象.png”、“02__weak指針引用對(duì)象.png”、“03__unsafe_unretained指針引用對(duì)象.png”三張圖表示。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-variable"
__strong ZYClass *strongZYClass;
__weak ZYClass *weakZYClass;
__unsafe_unretained ZYClass *unsafeZYClass;
#pragma clang diagnostic pop
NSLog(@"test begin");
{
ZYClass *zyClass = [[ZYClass alloc] init];
strongZYClass = zyClass;
// weakZYClass = zyClass;
// unsafeZYClass = zyClass;
}
NSLog(@"test over%@",strongZYClass);
}
"zyClass"定義的作用域如下:
{
ZYClass *zyClass = [[ZYClass alloc] init];
strongZYClass = zyClass;
// weakZYClass = zyClass;
// unsafeZYClass = zyClass;
}
鑒于__strong
指針對(duì)對(duì)象有強(qiáng)引用關(guān)系,所以"zyClass"在出作用域后并沒有立即銷毀;
__weak
指針對(duì)對(duì)象是弱引用關(guān)系,不持有引用對(duì)象。所以"zyClass"在出作用域后就銷毀了;
__unsafe_unretained
指針對(duì)對(duì)象是弱引用關(guān)系,不持有引用對(duì)象。所以"zyClass"在出作用域后就銷毀了。(與__weak不同的是,__weak引用的對(duì)象銷毀后,系統(tǒng)會(huì)將對(duì)象置為nil,而__unsafe_unretained不這么做,導(dǎo)致EXC_BAD_ACCESS
錯(cuò)誤。)
weak指針幫我們干了啥?
當(dāng)一個(gè)對(duì)象釋放的時(shí)候,會(huì)執(zhí)行"- (void)dealloc {}"方法,在objc源碼的“NSObject.mm”中找到了該函數(shù)以及相關(guān)調(diào)用流程,我將它們抽取出來如下:
// Replaced by NSZombies
- (void)dealloc {
_objc_rootDealloc(self);
}
void _objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
inline void objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
id object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory.
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
**********************************************************************/
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
//清除對(duì)象的成員變量
if (cxx) object_cxxDestruct(obj);
//清除對(duì)象的關(guān)聯(lián)對(duì)象
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
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();
}
assert(!sidetable_present());
}
// Slow path of clearDeallocating()
// for objects with nonpointer isa
// that were ever weakly referenced
// or whose retain count ever overflowed to the side table.
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);
}
table.unlock();
}
/**
* Called by dealloc; nils out all weak pointers that point to the
* provided object so that they can no longer be used.
*
* @param weak_table
* @param referent The object being deallocated.
*/
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) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}
// zero out references
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;
}
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_entry_remove(weak_table, entry);
}
/**
* Return the weak reference table entry for the given referent.
* If there is no entry for referent, return NULL.
* Performs a lookup.
*
* @param weak_table
* @param referent The object. Must not be nil.
*
* @return The table of weak referrers to this object.
*/
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];
}
函數(shù)inline void objc_object::rootDealloc()
中有一句判斷if (isTaggedPointer()) return; // fixme necessary?
,這個(gè)地方條件成立就會(huì)return ,而不會(huì)釋放對(duì)象,為什么?
實(shí)際上蘋果在64位系統(tǒng)開始推出了“Tagged Pointer”技術(shù)來優(yōu)化NSNumber、NSString、NSDate等小對(duì)象的存儲(chǔ),在沒有引入“Tagged Pointer”技術(shù)之前,NSNumber等對(duì)象需要?jiǎng)討B(tài)分配內(nèi)存、維護(hù)引用技術(shù),NSNumber指針存儲(chǔ)的是NSNumber對(duì)象的地址值。iOS引入“Tagged Pointer”技術(shù)之后,NSNumber指針里面存儲(chǔ)的數(shù)據(jù)變成了“Tag+Data”,也就是直接將數(shù)據(jù)存儲(chǔ)在指針中。僅當(dāng)指針不夠存儲(chǔ)數(shù)據(jù)時(shí),才會(huì)使用動(dòng)態(tài)分配內(nèi)存的方式來存儲(chǔ)數(shù)據(jù)。
“Tagged Pointer”的好處是:一方面節(jié)約計(jì)算機(jī)內(nèi)存,另一方面因?yàn)榭梢灾苯訌闹羔樦凶x取數(shù)據(jù),可以節(jié)約之前objc_msgSend流程消耗的時(shí)間。
那對(duì)于下面三個(gè)“對(duì)象”(這里注意“”修飾,因?yàn)閍、b本質(zhì)上屬于Tagged Pointer類型,而不是OC對(duì)象):
a. NSNumber *number1 = @4;
b. NSNumber *number2 = @5;
c. NSNumber *number3 = @(0xFFFFFFFFFFFFFFF)。
對(duì)于a、b,所對(duì)應(yīng)的二級(jí)制編碼分別為0b0100、0b0101,僅僅占用3 bit,用一個(gè)字節(jié)(8bit)就足夠存儲(chǔ)了,而OC的指針*number1、*number2
都占用8個(gè)字節(jié),因此我們完全可以將a、b的值存放在指針中,那么a、b實(shí)際上就不是真正的對(duì)象了,也就不存在執(zhí)行所謂的- (void) dealloc{}
流程。如果按照64位之前的策略,那存儲(chǔ)a、b這樣的小對(duì)象,需要在堆空間alloc init出一個(gè)NSNumber對(duì)象,然后將@10放入對(duì)象中,這個(gè)過程至少占用16字節(jié),然后在棧區(qū)用一個(gè)指針指向這個(gè)NSNumber對(duì)象,??臻g指針又占用8字節(jié),所以至少需要24字節(jié)存儲(chǔ)a、b這樣的小對(duì)象,很浪費(fèi)內(nèi)存。詳細(xì)請(qǐng)參考談?wù)勎覍?duì)Objective-C對(duì)象本質(zhì)的理解。
回到正題:
當(dāng)一個(gè)對(duì)象被回收的時(shí)候調(diào)用流程:
1 -(void)dealloc ->
2 _objc_rootDealloc(id obj) ->
3 objc_object::rootDealloc() ->
4 object_dispose(id obj) ->
5 objc_destructInstance(id obj) ->
6 objc_object::clearDeallocating() ->
7 objc_object::clearDeallocating_slow() ->
8 weak_clear_no_lock(weak_table_t weak_table, id referent_id) ->
**9 weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
前面1~6都很好理解。7開始到關(guān)鍵點(diǎn):從SideTable取出weak_table和當(dāng)前對(duì)象指針"this"當(dāng)做實(shí)參傳給函數(shù)weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
,該函數(shù)利用weak_table
和轉(zhuǎn)換后的referent_id
對(duì)象調(diào)用weak_entry_t * weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
得到一個(gè)關(guān)于referent_id
對(duì)象的weak引用表的數(shù)組。
再看一次"weak_entry_t * weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)"函數(shù)實(shí)現(xiàn)細(xì)節(jié)
函數(shù)內(nèi)部利用正在被dealloc的對(duì)象地址referent 通過哈希函數(shù)hash_pointer()計(jì)算,再&weak_table->mask獲得begin索引,所以可推測(cè)weak_table是一個(gè)散列表結(jié)構(gòu),weak_table來自于SideTable對(duì)象中的“weak_table”成員,有了這個(gè)當(dāng)前對(duì)象在散列表中的索引,就可以通過索引獲取當(dāng)前對(duì)象的弱引用數(shù)組了(當(dāng)然根據(jù)獲取到的begin索引得到的散列結(jié)果可能并不是這個(gè)“dealloc對(duì)象”的,因?yàn)榇嬖谏⒘袥_突,所以這里面有while ()循環(huán)判斷當(dāng)前index散列值的“ referent”與我們傳入的“ referent”是否匹配)。
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;
}
}
通過這個(gè)while循環(huán),可見這個(gè)散列表解決散列沖突采用的是“開放尋址法”。
總結(jié):程序運(yùn)行時(shí)將弱引用存到一個(gè)哈希表中,當(dāng)對(duì)象obj要銷毀的時(shí)候,哈希函數(shù)根據(jù)obj地址獲取到索引,然后從哈希表中取出obj對(duì)應(yīng)的弱引用集合weak_entries,遍歷weak_entries并一一清空(也就對(duì)應(yīng)源碼中函數(shù)void weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
所做的*referrer = nil;
)。