Runtime系列(一):浅谈Objective-C中对象的生命周期

本系列博客是本人的开发笔记。为了方便讨论,本人新建了一个微信群(iOS技术讨论群),想要加入的,请添加本人微信:zhujinhui207407,【加我前请备注:iOS 】,本人博客http://www.kyson.cn 也在不停的更新中,欢迎一起讨论

广告

我的首款个人开发的APP壁纸宝贝上线了,欢迎大家下载。

Runtime系列(一):浅谈Objective-C中对象的生命周期_第1张图片
壁纸宝贝

引言

Objective-C是面向对象编程的语言,我们每天要写很多诸如

[[XXX alloc] init];

等方法来创建对象。那么这些熟悉的方法中蕴含着底层的逻辑是怎么样实现的呢,今天笔者就跟大家探讨一下。

提前准备

runtime源码706
该源码是在苹果官网上下载的,但苹果官网提供的版本不能编译通过,因此笔者还引入了一些项目中需要的头文件。

Runtime系列(一):浅谈Objective-C中对象的生命周期_第2张图片
image

如图,打开工程后选择工程 debug-objc,点击run即可。由于 debug-objc依赖于objc(即runtime的源代码编译的库),因此我们在main函数中所有Objective-C的代码会调用我们编译的runtime,从而方便我们调试。

小试牛刀

在我们的main.m中,输入以下代码:

#import 

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [[NSObject alloc] init];
    }
    return 0;
}

打断点后,我们会发现NSObject *obj = [[NSObject alloc] init];方法最终会调用到runtime中的NSObject.mm中。也就是说,对象的创建都是在NSObject.mm中完成的。浏览这个文件的函数列表,发现以下函数都有实现:

- (id)copy;
- (id)mutableCopy;
- (instancetype)retain;
- (oneway void)release;
- (instancetype)autorelease;

本文也主要会讲解NSObject.mm这个文件。

对象创建(alloc)

Runtime系列(一):浅谈Objective-C中对象的生命周期_第3张图片
image

alloc内部的调用栈大概如上图所示,,也不需要全都记住,主要知道几个关键参数,关键条件和关键实现就可以了。

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    if (slowpath(checkNil && !cls)) return nil;
    
#if __OBJC2__
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        // No alloc/allocWithZone implementation. Go straight to the allocator.
        // fixme store hasCustomAWZ in the non-meta class and
        // add it to canAllocFast's summary
        if (fastpath(cls->canAllocFast())) {
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor();
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            // Has ctor or raw isa or something. Use the slower path.
            id obj = class_createInstance(cls, 0);
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            return obj;
        }
    }
#endif
    // No shortcuts available.
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}
  • hasCustomAWZ存在于类的元类中,标识这个类有没有复写alloc/allocWithZone:;

  • canAllocFast是否支持快速创建.

  • slowpath、fastpath 实际上是__builtin_expect(),一个GCC的一个内建函数,用于分支预测优化。只需要知道if(fastpath(x)) 与 if(x)是相同意思就可以了。

可以看出最终都调用了_class_createInstanceFromZone

static __attribute__((always_inline)) 
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if (!cls) return nil;
    assert(cls->isRealized());
    // Read class's info bits all at once for performance
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();

    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}

几大判断条件:

  1. hasCxxCtor:类及父类是否有自己的构造函数;
  2. hasCxxDtor:类及父类是否有自己的析构函数(这个条件在后面讲对象dealloc的时候也会说到,与对象是否有实例变量有关,这条件会记录在对象的isa内);
    fast:类是否用了是优化的isa;

其他:

  1. zone:老版本中要先去看看zone是否有空间,在OBJC2下,忽略zone参数。

发现最终调用的是

obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);

其中:size:这由外围传入,size存储在类的元类内。这里对元类不做过多介绍,会在以后的文章中给与补充。

增加引用计数(retain)

在讲解retain,copy等一系列和引用计数有关的方法前,先回顾一下引用计数的概念。

引用计数

引用计数:Objective-C中引入了引用计数这一机制来跟踪并处理对象的生命周期,
管理方式:每个对象都有一个与之关联的整数,这个整数被称为引用计数,在Objective-C中,通过不同的方法可以对引用计数进行操作,具体的处理如下表:

对象操作 Objective-C方法 对应的操作结果
生成并持有对象 alloc, new, copy,mutableCopy等方法 生成对象并设置引用计数 =1
持有对象 reatain方法 使引用计数 +1
释放对象 release方法 使引用计数 -1
废弃对象 dealloc方法 系统自动调用 引用计数 =0 时调用

在引用计数中,每一个对象负责维护对象所有引用的计数值。当一个新的引用指向对象时,引用计数器就递增,当去掉一个引用时,引用计数就递减。当引用计数到零时,该对象就将释放占有的资源。

采用上述机制看似就可以知道对象在内存中应该何时释放了,但是还有一个循环引用的问题需要我们解决。


Runtime系列(一):浅谈Objective-C中对象的生命周期_第4张图片
image

假设现在内存中有两个对象,UIViewController和UITableView。

//viewController添加子视图即引用了tableview
[viewController.view addSubView: tableview]; 
tableview.delegate = viewController;

如果delegate使用强引用,就会出现循环引用的问题。为了解决这个问题,我们引入了弱引用,即weak概念,例如刚刚的delegate问题,我们需要声明delegate为weak对象:

@property (nonatomic, weak) id  dataSource;
@property (nonatomic, weak) id  delegate;

这样两个对象互相循环引用着对方谁都不会被释放就造成了内存泄露。为了解决这个问题我们来引入弱引用的概念。弱引用指向要引用的对象,但是不会增加那个对象的引用计数。如下图,虚线为弱引用

Runtime系列(一):浅谈Objective-C中对象的生命周期_第5张图片
image

这样就循环引用的问题,还有一个问题就是野指针的问题。
假想一个场景:某个页面进入后会做网络请求。如果我们进入某个ViewController后,加载速度很慢,忍受不了,又迅速关掉该页面,但该页面的网络请求还在继续,等请求成功后回调再设置ViewController的属性或进行其他操作的时候因为没有观察到ViewController已经释放,而产生了crash:

Runtime系列(一):浅谈Objective-C中对象的生命周期_第6张图片
image

笔者也写了个Demo,用于展示该crash的产生:
WlldPointerDemo
Demo中从tableviewcontroller push到viewcontroller后迅速返回,就能模拟网络请求成功回调后,调用viewcontroller方法导致crash的问题。原因就是networkservice的delegate设置成了assign,而不是weak:
Runtime系列(一):浅谈Objective-C中对象的生命周期_第7张图片
image

关于assign关键字,这里只做个简单介绍,大家有兴趣可以了解一下:
assign与weak区别

assign适用于基本数据类型,weak是适用于NSObject对象,并且是一个弱引用。
assign其实也可以用来修饰对象。我们不用它修饰对象的原因是被assign修饰的对象(一般编译的时候会产生警告:Assigning retained object to unsafe property; object will be released after assignment)在释放之后,指针的地址还是存在的,也就是说指针并没有被置为nil,造成野指针。对象一般分配在堆上的某块内存,如果在后续的内存分配中,刚好分到了这块地址,程序就会崩溃掉。不过我们还是可以用assign修饰基本数据类型是,因为基础数据类型一般分配在栈上,栈的内存会由系统自己自动处理,不会造成野指针。
weak修饰的对象在释放之后,指针地址会被置为nil。所以现在一般弱引用就是用weak。

管理引用计数

为了管理所有对象的引用计数和weak指针,苹果创建了一个全局的SideTables,数据结构是一个全局的Hash表,里面的内容装的都是SideTable结构体。它使用对象的内存地址当它的key。用于管理引用计数和weak指针。
因为对象引用计数相关操作应该是原子性的。不然如果多个线程同时去写一个对象的引用计数,那就会造成数据错乱,失去了内存管理的意义。同时又因为内存中对象的数量是非常非常庞大的需要非常频繁的操作SideTables,所以不能对整个Hash表加锁。苹果采用了分离锁技术。

分离锁和分拆锁的区别
降低锁竞争的另一种方法是降低线程请求锁的频率。分拆锁 (lock splitting) 和分离锁 (lock striping) 是达到此目的两种方式。相互独立的状态变量,应该使用独立的锁进行保护。有时开发人员会错误地使用一个锁保护所有的状态变量。这些技术减小了锁的粒度,实现了更好的可伸缩性。但是,这些锁需要仔细地分配,以降低发生死锁的危险。
如果一个锁守护多个相互独立的状态变量,你可能能够通过分拆锁,使每一个锁守护不同的变量,从而改进可伸缩性。通过这样的改变,使每一个锁被请求的频率都变小了。分拆锁对于中等竞争强度的锁,能够有效地把它们大部分转化为非竞争的锁,使性能和可伸缩性都得到提高。
分拆锁有时候可以被扩展,分成若干加锁块的集合,并且它们归属于相互独立的对象,这样的情况就是分离锁。

因为是使用对象的内存地址当key所以Hash的分部也很平均。假设Hash表有n个元素,则可以将Hash的冲突减少到n分之一,支持n路的并发写操作。


Runtime系列(一):浅谈Objective-C中对象的生命周期_第8张图片
image

SideTable概念

我们可以通过SideTables[key]来得到SideTable:

SideTable& table = SideTables()[this];

其中SideTable定义如下:

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;
    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }
    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }
    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }
    // Address-ordered lock discipline for a pair of side tables.
    template
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

这段代码需要对C++有一定基础知识,例如引用,操作符重载等知识。不在本文的讨论范围,大家可以课后研究。

自旋锁

自旋锁比较适用于锁使用者保持锁时间比较短的情况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用,而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。

它的作用是在操作引用技术的时候对SideTable加锁,避免数据错误。
苹果在对锁的选择上可以说是精益求精。苹果知道对于引用计数的操作其实是非常快的。所以选择了虽然不是那么高级但是确实效率高的自旋锁。

引用计数器 RefcountMap
对象具体的引用计数数量是记录在这里的。
RefcountMap其实是个C++的Map。为什么Hash以后还需要个Map?这里苹果采用的是分块化的方法:
苹果把两个对象的内存管理都放到里同一个SideTable中。你在这个SideTable中需要再次调用table.refcnts.find(0x0000)或者table.refcnts.find(0x000f)来找到他们真正的引用计数器。
这里是一个分流。内存中对象的数量实在是太庞大了我们通过第一个Hash表只是过滤了第一次,然后我们还需要再通过这个Map才能精确的定位到我们要找的对象的引用计数器。

weak对象实现

了解SideTables是了解weak对象实现的前提,因为weak对象都在sidetables中存储的:weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址数组。那么weak的原理是什么呢?下面就分析一下weak的工作原理:
weak 的实现原理可以概括一下三步:

  1. 初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
  2. 添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
  3. 释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

下面将开始详细介绍每一步:

  1. 初始化时:runtime会调用objc_initWeak函数,objc_initWeak函数会初始化一个新的weak指针指向对象的地址。
    示例代码:
NSObject *obj = [[NSObject alloc] init];
id __weak obj1 = obj;

当我们初始化一个weak变量时,runtime会调用 NSObject.mm中的objc_initWeak函数:

id objc_initWeak(id *location, id newObj) {
// 查看对象实例是否有效
// 无效对象直接导致指针释放
    if (!newObj) {
        *location = nil;
        return nil;
    }
    // 这里传递了三个 bool 数值
    // 使用 template 进行常量参数传递是为了优化性能
    return storeWeakfalse/*old*/, true/*new*/, true/*crash*/>
    (location, (objc_object*)newObj);
}
  1. 添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。

objc_storeWeak的函数实现如下:

// HaveOld:     true - 变量有值
//             false - 需要被及时清理,当前值可能为 nil
// HaveNew:     true - 需要被分配的新值,当前值可能为 nil
//             false - 不需要分配新值
// CrashIfDeallocating: true - 说明 newObj 已经释放或者 newObj 不支持弱引用,该过程需要暂停
//             false - 用 nil 替代存储
template bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj) {
    // 该过程用来更新弱引用指针的指向
    // 初始化 previouslyInitializedClass 指针
    Class previouslyInitializedClass = nil;
    id oldObj;
    // 声明两个 SideTable
    // ① 新旧散列创建
    SideTable *oldTable;
    SideTable *newTable;
    // 获得新值和旧值的锁存位置(用地址作为唯一标示)
    // 通过地址来建立索引标志,防止桶重复
    // 下面指向的操作会改变旧值
retry:
    if (HaveOld) {
        // 更改指针,获得以 oldObj 为索引所存储的值地址
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (HaveNew) {
        // 更改新值指针,获得以 newObj 为索引所存储的值地址
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }
    // 加锁操作,防止多线程中竞争冲突
    SideTable::lockTwoHaveOld, HaveNew>(oldTable, newTable);
    // 避免线程冲突重处理
    // location 应该与 oldObj 保持一致,如果不同,说明当前的 location 已经处理过 oldObj 可是又被其他线程所修改
    if (HaveOld  &&  *location != oldObj) {
        SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
        goto retry;
    }
    // 防止弱引用间死锁
    // 并且通过 +initialize 初始化构造器保证所有弱引用的 isa 非空指向
    if (HaveNew  &&  newObj) {
        // 获得新对象的 isa 指针
        Class cls = newObj->getIsa();
        // 判断 isa 非空且已经初始化
        if (cls != previouslyInitializedClass  &&
            !((objc_class *)cls)->isInitialized()) {
            // 解锁
            SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
            // 对其 isa 指针进行初始化
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));
            // 如果该类已经完成执行 +initialize 方法是最理想情况
            // 如果该类 +initialize 在线程中
            // 例如 +initialize 正在调用 storeWeak 方法
            // 需要手动对其增加保护策略,并设置 previouslyInitializedClass 指针进行标记
            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 方法返回 nil
        // 在引用计数表中设置若引用标记位
        if (newObj  &&  !newObj->isTaggedPointer()) {
            // 弱引用位初始化操作
            // 引用计数那张散列表的weak引用对象的引用计数中标识为weak引用
            newObj->setWeaklyReferenced_nolock();
        }
        // 之前不要设置 location 对象,这里需要更改指针指向
        *location = (id)newObj;
    }
    else {
        // 没有新值,则无需更改
    }
    SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
    return (id)newObj;
}

对以上代码的部分方法再做个介绍:

  • 旧对象解除注册操作 weak_unregister_no_lock
    该方法主要作用是将旧对象在 weak_table 中接触 weak 指针的对应绑定。根据函数名,称之为解除注册操作。从源码中,可以知道其功能就是从 weak_table 中接触 weak 指针的绑定。而其中的遍历查询,就是针对于 weak_entry 中的多张弱引用散列表。
  • 新对象添加注册操作 weak_register_no_lock
    这一步与上一步相反,通过 weak_register_no_lock 函数把心的对象进行注册操作,完成与对应的弱引用表进行绑定操作。

理解了SideTables,那retain方法自然也能很好理解了:

给retain方法打断点

由于现在我们使用ARC,因此如果对一个对象调用retain方法会报错的,解决方案就是关闭ARC:


Runtime系列(一):浅谈Objective-C中对象的生命周期_第9张图片
image

如上图,将蓝色区域设置为No即可。

retain方法源码如下:

ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    if (isTaggedPointer()) return (id)this;
    bool sideTableLocked = false;
    bool transcribeToSideTable = false;
    isa_t oldisa;
    isa_t newisa;
    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain();
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            return nil;
        }
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++
        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if (!handleOverflow) {
                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 (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();
    return (id)this;
}

对以上代码做个大致分析:
TaggedPointer
在WWDC2013中,苹果介绍了NSTaggedPointer,在32位时代,一个指针占4个字节,即32位。到了64位时代,一个指针被扩大到了8个字节,即64位。也就是说就算什么都不干,仅仅是把以前的代码放到64位系统上运行,内存占用也会扩大一倍。而在绝大多数情况下,我们并不需要64位去存储指针的地址,我们完全可以像non-pointer isa一样,去存点别的东西,比如说小对象本身的值,即对象的"指针"本身就已经带了值,不仅充分利用了内存空间,而且更美妙的是还不用去二次查找,这也加快了值的访问速度,还减去了开辟内存,销毁的开销,这就是NSTaggedPointer的设计思想。

NSTaggedPointer特点
(1)NSTaggedPointer是在64位系统中被加入的,它专门用于存储一些小的对象,如NSNumber,NSDate。
(2)NSTaggedPointer把对象的值本身存在了pointer里面,没有malloc和free的消耗(也不会存在堆中)。
(3)在性能上,它有三倍的内存使用效率,以及106倍的生成和销毁效率。

LoadExclusive

LoadExclusive(&isa.bits)
static uintptr_t LoadExclusive(uintptr_t *src)
{
    return *src;
}

很简单,就是获取isa的内容。这里既然讲到isa,就大概提一下:

  • has_assoc 表示该对象是否包含 关联对象
  • has_cxx_dtor 表示 该对象是否有 C++ 或者 Objc 的析构器
  • shiftcls 类的指针
  • magic 判断对象是否初始化完成
  • weakly_referenced 对象是否被指向一个弱变量
  • deallocating 对象正在释放内存
  • has_sidetable_rc 判断该对象的引用计数是否过大,如果过大则需要其他散列表来进行存储
  • extra_rc存放该对象的引用计数值减一后的结果

addc
这个方法会调用文件objc-os.h内部的方法__builtin_addcl,这里不开源,不做过多研究,但调用该方法后,extra_rc 便增加了一,也就是引用计数增加了。
因此大概可以总结一下:正常情况下,retain方法就是给extra_rc加1,当extra_rc溢出时,将一半的引用计数存放到SideTable中。

我们在main函数中编写代码如下:

NSObject *obj = [[NSObject alloc] init];
[obj retain];

再打断点可以发现,只有

newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

被调用了,其他的if分支并没有进入,但有时候我们会进入第一个分支,

 if (slowpath(!newisa.nonpointer)) {

并调用

sidetable_retain();

其内部实现如下:

id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
//1、通过对象内存地址,在SideTables找到对应的SideTable
    SideTable& table = SideTables()[this];
    table.lock();
//2、通过对象内存地址,在refcnts中取出引用计数
//这里是table是SideTable。refcnts是RefcountMap
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();
//3、判断PINNED位,不为1则+4
    return (id)this;
}

可以发现,又进入到了我们之前描述的SideTable部分了。

其他的相关方法也一并带过,大家可以了解一下:

dealloc方法:

SideTable& table = SideTables()[this];
table.lock();
引用计数器 = table.refcnts.find(this);
if (引用计数器 != table.refcnts.end()) {
    if (引用计数器中SIDE_TABLE_WEAKLY_REFERENCED标志位为1) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    //从refcnts中删除引用计数器
    table.refcnts.erase(it);
}
table.unlock();

weak_clear_no_lock()会在对象被销毁的时候处理所有弱引用指针。

void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    //1、拿到被销毁对象的指针
    objc_object *referent = (objc_object *)referent_id;

    //2、通过 指针 在weak_table中查找出对应的entry
    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;
    }

    //3、将所有的引用设置成nil
    weak_referrer_t *referrers;
    size_t count;
    
    if (entry->out_of_line()) {
        //3.1、如果弱引用超过4个则将referrers数组内的弱引用都置成nil。
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        //3.2、不超过4个则将inline_referrers数组内的弱引用都置成nil
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    //循环设置所有的引用为nil
    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();
            }
        }
    }
    
    //4、从weak_table中移除entry
    weak_entry_remove(weak_table, entry);
}

参考

objc - 编译Runtime源码objc4-680
iOS管理对象内存的数据结构以及操作算法--SideTables、RefcountMap、weak_table_t-一
iOS管理对象内存的数据结构以及操作算法--SideTables、RefcountMap、weak_table_t-二
iOS Objective-C底层 part1:start
iOS Objective-C底层 part2:born
OC源码 —— retain和release
__builtin_expect — 分支预测优化
iOS 底层解析weak的实现原理(包含weak对象的初始化,引用,释放的分析)

广告

我的首款个人开发的APP壁纸宝贝上线了,欢迎大家下载。

Runtime系列(一):浅谈Objective-C中对象的生命周期_第10张图片
壁纸宝贝

你可能感兴趣的:(Runtime系列(一):浅谈Objective-C中对象的生命周期)