作為一個iOS開發(fā),相信大家對OC ARC下的weak弱引用都有所了解,底層會有SideTable來保存弱引用指針,當對象被釋放時,會清空這個弱引用表,在往下看之前,這些內容是要熟悉的.
今天這個問題我先用一個demo引出
main.h
__weak Person *weakP;
int main(int argc, const char * argv[]) {
{
Person *p = [[Person alloc] init];
p.deallocCallBack = ^{
NSLog(@"%@",weakP);
};
weakP = p;
}
return 0;
}
Person
@interface Person : NSObject
@property(nonatomic, copy) void(^deallocCallBack)(void);
@end
@implementation Person
-(void)dealloc {
self.deallocCallBack();
}
@end
請問在deallocCallBack中打印的weakP是什么
結論是nil,你沒有聽錯,是nil
正常情況下理解會打印Person對象,因為weak被清空是在對象的-dealloc函數(shù)執(zhí)行完,編譯器會在結尾添加[super dealloc]的調用從而執(zhí)行NSObject的dealloc方法,進而去清空弱引用表
- (void)dealloc {
_objc_rootDealloc(self);
}
void _objc_rootDealloc(id 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);
}
#endif // ISA_HAS_INLINE_RC
}
id object_dispose(id obj) {
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return 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.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_associations(obj, /*deallocating*/true);
obj->clearDeallocating();
}
return obj;
}
inline void objc_object::clearDeallocating() {
if (slowpath(isa().weakly_referenced || isa().has_sidetable_rc) {
clearDeallocating_slow();
}
assert(!sidetable_present());
}
這一連串的代碼,關于weak的問題就是一句話
弱引用表真實的處理是在對象-(void)dealloc執(zhí)行完之后才開始的
如果是dealloc調用之后才開始清空弱引用表,那為什么在dealloc中調用block回調中打印weakP就已經(jīng)是nil了呢,這里就涉及到第一個關鍵點
我們平時在使用weak對象的時候,并不是直接取出對象地址直接使用,而是對weak對象地址進行了處理
調用了objc_loadWeakRetained函數(shù)
id
objc_loadWeakRetained(id *location)
{
id obj;
id result;
Class cls;
SideTable *table;
obj = *location;
// 如果傳進來的就是小對象或者本身就是nil,直接返回
if (_objc_isTaggedPointerOrNil(obj)) return obj;
table = &SideTables()[obj];
result = obj;
// 獲取類對象
cls = obj->ISA();
if (! cls->hasCustomRR()) {
// 正常情況不會自定義,所以會進來
if (! obj->rootTryRetain()) {
result = nil;
}
}
else {
// ... 省略
}
return result;
}
id objc_object::rootRetain(bool tryRetain (true), objc_object::RRVariant variant(Fast))
{
if (slowpath(isTaggedPointer())) return (id)this;
isa_t oldisa;
isa_t newisa;
oldisa = LoadExclusive(&isa().bits);
do {
//---------------------------------
transcribeToSideTable = false;
newisa = oldisa;
if (slowpath(newisa.isDeallocating())) {
ClearExclusive(&isa().bits);
if (sideTableLocked) {
sidetable_unlock();
}
if (slowpath(tryRetain)) {
return nil;
} else {
return (id)this;
}
}
//---------------------------------
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (variant != RRVariant::Full) {
ClearExclusive(&isa().bits);
return rootRetain_overflow(tryRetain);
}
// Leave half of the retain counts inline and
// prepare to copy the other half to the side table.
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa().bits, &oldisa.bits, newisa.bits)));
if (variant == RRVariant::Full) {
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
} else {
ASSERT(!transcribeToSideTable);
ASSERT(!sideTableLocked);
}
return (id)this;
}
重點關注虛線標注的代碼
bool isDeallocating() const {
return extra_rc == 0 && has_sidetable_rc == 0;
}
可以發(fā)現(xiàn)如果對象的引用計數(shù)是0,就會返回nil
到這里我們可以理解為weak的使用是會先判斷對象的引用計數(shù)的,并不是直接拿來對象地址就直接用了
下一步我們就需要關注引用計數(shù),dealloc之間的關系了,引用計數(shù)在什么時候--的,-(void)dealloc又是在什么時候調用的,繼續(xù)看源碼
先看下release,最終調用rootRelease,對函數(shù)簡化如下
bool objc_object::rootRelease(bool performDealloc(true), objc_object::RRVariant variant(FastOrMsgSend))
{
if (slowpath(isTaggedPointer())) return false;
bool sideTableLocked = false;
isa_t newisa, oldisa;
oldisa = LoadExclusive(&isa().bits);
do {
newisa = oldisa;
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
} while (slowpath(!StoreReleaseExclusive(&isa().bits, &oldisa.bits, newisa.bits)));
if (slowpath(newisa.isDeallocating()))
goto deallocate;
if (variant == RRVariant::Full) {
if (slowpath(sideTableLocked)) sidetable_unlock();
} else {
ASSERT(!sideTableLocked);
}
return false;
deallocate:
if (performDealloc) {
this->performDealloc();
}
return true;
}
這里重點關注newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);可以看到備注// extra_rc--,說明這個是真正操作extra_rc的函數(shù),事實證明確實是,斷點發(fā)現(xiàn)調用后extra_rc變成了 0
一下主要關注if (slowpath(newisa.isDeallocating())) goto deallocate;
因為先執(zhí)行subc,將extra_rc--變成了0,所以newisa.isDeallocating()結果是true,結果是跳轉到deallocate開始執(zhí)行performDealloc
void objc_object::performDealloc() {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
呀,這不是-(void) dealloc嘛
總結:作用域結束person 調用release,進行了extra_rc - 1變成了0,然后調用-(void) dealloc,所以結合上面的block調用,在block執(zhí)行的時候extra_rc已經(jīng)變成了0,就導致newisa.isDeallocating()是true,返回了nil