美文网首页程序员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源码(一) 引用计数相关

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