美文网首页程序员iOS面试
iOS源码(一) 引用计数相关

iOS源码(一) 引用计数相关

作者: 运气不够技术凑 | 来源:发表于2018-09-03 16:16 被阅读111次

iOS内存管理

引用计数(Reference Count)是一个简单而有效的管理对象生命周期的方式。当我们创建一个新对象的时候,它的引用计数为 1,当有一个新的指针指向这个对象时,我们将其引用计数加 1,当某个指针不再指向这个对象是,我们将其引用计数减 1,当对象的引用计数变为 0 时,说明这个对象不再被任何指针指向了,这个时候我们就可以将对象销毁,回收内存。

结构图

33BF9731-DA8B-4377-9C07-BFB2E649D6F8.png 422CAD4D-7953-44F5-81AF-5D01810474B1.png

下面对 SideTable 里的部分内容进行介绍

SideTables

为了管理所有对象的引用计数和weak指针,苹果创建了一个全局的SideTables,虽然名字后面有个"s",但是并不代表它有很多个表,它其实是一个全局的Hash表,里面的内容装的都是SideTable结构体而已。它使用对象的内存地址当它的key。管理引用计数和weak指针就靠它了。

SideTables哈希表里面还包含一个c++的map:RefcountMap, 这个是为了解决哈希冲突。
假设现在内存中有16个对象。
0x0000、0x0001、...... 0x000e、0x000f
咱们创建一个SideTables[8]来存放这16个对象,那么查找的时候发生Hash冲突的概率就是八分之一。
假设SideTables[0x0000]和SideTables[0x0x000f]冲突,映射到相同的结果。
SideTables[0x0000] == SideTables[0x0x000f] ==> 都指向同一个SideTable

苹果把两个对象的内存管理都放到里同一个SideTable中。你在这个SideTable中需要再次调用table.refcnts.find(0x0000)或者table.refcnts.find(0x000f)来找到他们真正的引用计数器。这里是一个分流。内存中对象的数量实在是太庞大了我们通过第一个Hash表只是过滤了第一次,然后我们还需要再通过这个Map才能精确的定位到我们要找的对象的引用计数器。(不是很精确的总结就是说要进行两次map的查找)

关于引用计数的类型:size_t

size_type由string类类型和vector类类型定义的类型,用以保存任意string对象或vector对象的长度,标准库类型将size_type定义为unsigned类型。size_t在32位系统上定义为 unsigned int;在64位系统上定义为 unsigned long,也就是说 size_t 在32位系统上是32位,在64位上是64位,


435542AA-FD9A-4833-91E7-E8B8DD707DE2.png

关于spinlock_t 锁的使用

内存中对象的数量是非常非常庞大的需要非常频繁的操作 SideTables,所以不能对整个 Hash 表加锁。苹果采用了分离锁技术。 类似于java的ConcurrentHashMap的锁分离技术,concurrenthashmap是一个非常好的map实现,在高并发操作的场景下会有非常好的效率。实现的目的主要是为了避免同步操作时对整个map对象进行锁定从而提高并发访问能力。

ConcurrentHashMap将hash表分为16个桶(默认值),诸如get,put,remove等常用操作只锁当前需要用到的桶。试想,原来 只能一个线程进入,现在却能同时16个写线程进入(写线程才需要锁定,而读线程几乎不受限制,之后会提到),并发性的提升是显而易见的。

关于weak_table_t:

A53B75B3-B2CB-4075-BF5C-0901C5B5D589.png

weak

id __week obj1 = obj;

那么编译器将会进行一些操作:

id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_storeWeak(&obj1, 0);

objc_storeWeak

函数把第二参数的赋值对象的地址作为键值,将第一参数的附有__weak修饰的变量的地址注册到weak表中。
如果第二参数为0,则把变量的地址从weak表中删除。

D6FB7487-2C32-4EC6-91C9-EED13D4F8412.png

objc_storeWeak源码解析(部分代码的作用我写了注释,可以耐心读下)

storeWeak(id *location, objc_object *newObj)
{
    assert(haveOld  ||  haveNew);
    if (!haveNew) assert(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

    // 给旧值和新值加锁
    // 按顺序锁定地址来防止锁定排序问题。
    // 如果旧值在我们下面改变,请重试。
    retry:
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

// Prevent a deadlock between the weak reference machinery
// and the +initialize machinery by ensuring that no 
// weakly-referenced object has an un-+initialized isa.
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        _class_initialize(_class_getNonMetaClass(cls, (id)newObj));

        // If this class is finished with +initialize then we're good.
        // If this class is still running +initialize on this thread 
        // (i.e. +initialize called storeWeak on an instance of itself)
        // then we may proceed but it will appear initializing and 
        // not yet initialized to the check above.
        // Instead set previouslyInitializedClass to recognize it on retry.
            previouslyInitializedClass = cls;

            goto retry;
        }
    }

// Clean up old value, if any.
    if (haveOld) {
//weak_unregister_no_lock的作用
//取消注册已注册的弱引用。当引用者的存储即将释放,但引用对象还没有死亡的时候使用。  
//*@ PARAM弱表全局弱表。
//*@ PARAM引用对象。
//*@ PARAM引用弱引用。
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

// Assign new value, if any.
    if (haveNew) {
//weak_register_no_lock的作用
//注册一个新的键值对(object, weak pointer),如果不存在就创建新的弱引用对象
//*@ PARAM弱表全局弱表。
//*@ PARAM引用引用弱引用指向的对象。
//*@ PARAM引用弱指针地址。
        newObj = (objc_object *)
        weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                              crashIfDeallocating);
    // weak_register_no_lock 如果返回nil证明这个weak值应该被拒绝

    // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

    // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
    // No new value. The storage is not changed.
    }

    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj;
}

objc_destroyWeak

破坏弱指针和它在内部弱表中引用的对象之间的关系。如果弱指针不引用任何内容,则不需要编辑弱表。
对于弱变量的并发修改,此函数不是线程安全的。

有关一些引用计数的内容就是这些,摘取了一些其他博客的内容,整理了一下,有关weak的操作还有一些,这里就不一一贴出来了。

相关文章

  • iOS源码(一) 引用计数相关

    iOS内存管理 引用计数(Reference Count)是一个简单而有效的管理对象生命周期的方式。当我们创建一个...

  • iOS内存区域分布

    说到iOS的内存管理,大家首先想到的可能是引用计数相关的东西,而跟引用计数相关的内存都是分布在堆区(heap),也...

  • 引用计数相关几个问题

    引用计数相关几个问题(一) —— alloc init 引用计数引用计数相关几个问题(二) —— NSString...

  • iOS内存管理初探 – 引用计数、AutoRelease与ARC

    引用计数式内存管理 引用计数 iOS通过引用计数管理对象的生命周期,每个对象有其引用计数。 对象被强引用时引用计数...

  • iOS内存管理1:引用计数

    iOS内存管理1:引用计数 引用计数: Objecttive-C使用引用计数来进行内存管理。然后,引用计数其实是不...

  • Objective-C高级编程之内存管理篇

    iOS的内存管理是采用引用计数的方式,引用计数分为手动引用计数和自动引用计数(ARC)。前者要求开发者手动管理内存...

  • iOS概念攻坚之路(三):内存管理

    前言 iOS 的内存管理不止是 「引用计数表」。 iOS 开发者基本都知道 iOS 是通过「引用计数」来管理内存的...

  • 内存管理-MRC与ARC

    引用计数 在iOS中,使用引用计数来管理OC对象的内存1、一个新创建的OC对象引用计数默认是1,当引用计数减为0,...

  • ios内存管理记录

    ios的内存管理技术是自动引用计数也就是(Automatic Reference Counting,自动引用计数,...

  • iOS中引用计数相关问题

    alloc实现 经过一系列调用,最终调用了C函数的calloc方法此时并没有设置引用计数为1 retain实现 S...

网友评论

    本文标题:iOS源码(一) 引用计数相关

    本文链接:https://www.haomeiwen.com/subject/aexiwftx.html