作为一个iOS开发,相信大家对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函数执行完,编译器会在结尾添加[super dealloc]
的调用从而执行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
执行完之后才开始的
如果是dealloc调用之后才开始清空弱引用表,那为什么在dealloc中调用block回调中打印weakP就已经是nil了呢,这里就涉及到第一个关键点
我们平时在使用weak对象的时候,并不是直接取出对象地址直接使用,而是对weak对象地址进行了处理
调用了objc_loadWeakRetained
函数
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;
}
可以发现如果对象的引用计数是0,就会返回nil
到这里我们可以理解为weak的使用是会先判断对象的引用计数的,并不是直接拿来对象地址就直接用了
下一步我们就需要关注引用计数,dealloc之间的关系了,引用计数在什么时候--
的,-(void)dealloc
又是在什么时候调用的,继续看源码
先看下release,最终调用rootRelease
,对函数简化如下
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的函数,事实证明确实是,断点发现调用后extra_rc
变成了 0
一下主要关注if (slowpath(newisa.isDeallocating())) goto deallocate;
因为先执行subc
,将extra_rc--
变成了0,所以newisa.isDeallocating()
结果是true
,结果是跳转到deallocate
开始执行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执行的时候extra_rc已经变成了0,就导致newisa.isDeallocating()
是true
,返回了nil