arc中的weak进阶

作为一个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

总结:

weak属性的使用并不是直接拿出存储的地址直接使用的,或者说并不是直接跟弱引用表挂钩的,而是判断弱引用对象的引用计数,如果引用计数为0,即使弱引用表还存在,也会返回nil

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容