iOS-runtime详解(二)weak底层原理

全部流程

在这里插入图片描述

查看汇编

上次ARC的博客里涉及了__weak关键字,也简单了解了weak原理。
这篇博客来具体探究weak的底层原理。
在这里插入图片描述
查看汇编代码
在这里插入图片描述

查看源码

调用了initWeak、destroyWeak俩个方法
先看这个initWeak方法

* 
 * Initialize a fresh weak pointer to some object location. 
 * It would be used for code like: 
 *
 * (The nil case) 
 * __weak id weakPtr;
 * (The non-nil case) 
 * NSObject *o = ...;
 * __weak id weakPtr = o;
 * 
 * This function IS NOT thread-safe with respect to concurrent 
 * modifications to the weak variable. (Concurrent weak clear is safe.)
 *
 * @param location Address of __weak ptr. 
 * @param newObj Object ptr. 
 */
 翻译
 初始化一个指向某个对象位置的新弱指针。
*它将用于以下代码:
*
* (nil个案)
*剩余的弱项;
*(非nil个案)
* NSObject *o =…;
*剩余弱id;
*
*这个函数对于并发来说不是线程安全的
修改弱变量。(同时弱清晰是安全的。)
*
* @param剩余弱ptr的位置地址。
* @param newObj对象ptr。
id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

俩个参数是

location :__weak指针的地址,存储指针的地址,这样便可以在最后将其指向的对象置为nil。
newObj :所引用的对象。即例子中的obj 。

关键方法storeWeak

如果给__weak所指的对象为空了,就将这个对象置为nil。
再看看store这个方法

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

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

    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
 retry:
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
        //如果__weak指针之前弱引用过对象,就将这个对象对应的stideTable取出来,赋值给oldTable
    } else {
        oldTable = nil; //如果没有oldTable赋值为nil
    }
    if (haveNew) { //如果__weak指针要引用新对象,则将新对象的sideTable取出来赋值给newTable
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;  //如果__weak指针不需要引用新对象,则将newTable赋值为nil。
    }
//加锁操作,防止多线程中竞争冲突。
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
//location应与oldObj保持一致
//
//location这个参数是__weak指针,如果旧对象不为空且它不指向旧对象,说明已经被其他线程修改
    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())    //如果cls还没有初始化,先初始化,再尝试设置weak
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            class_initialize(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(&oldTable->weak_table, oldObj, location);
        //如果__weak指针之前弱引用过别的对象oldObj,则调用weak_unregister_no_lock,在oldObj的weak_entry_t中移除该__weak指针的地址
    }

    // Assign new value, if any.
    if (haveNew) { //如果__weak指针引用新对象
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);
                                  将__weak指针记录到newobject对应的weak_table对应的weak_entry_t中
        // weak_register_no_lock returns nil if weak store should be rejected
		//更新newObj的isa的weakly_referenced bit标志位
        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }
	//*location 赋值,也就是将__weak指针直接指向了newObj。可以看到,这里并没有将newObj的引用计数+1
        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    // 解锁,其他线程可以访问oldTable, newTable了
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
 // 返回newObj,此时的newObj与刚传入时相比,weakly-referenced bit位置1
    return (id)newObj;
}

总结用法

  • storeWeak方法实际上是接收了5个参数,分别是haveOld、haveNew和crashIfDeallocating,这三个参数都是以模板的方式传入的,是三个bool类型的参数。 分别表示weak指针之前是否指向了一个弱引用,weak指针是否需要指向一个新的引用,若果被弱引用的对象正在析构,此时再弱引用该对象是否应该crash。
  • 该方法维护了oldTable和newTable分别表示旧的引用弱表和新的弱引用表,它们都是SideTable的hash表。
  • 如果weak指针之前指向了一个弱引用,则会调用weak_unregister_no_lock方法将旧的weak指针地址移除。
  • 如果weak指针需要指向一个新的引用,则会调用weak_register_no_lock方法将新的weak指针地址添加到弱引用表中。
  • 调用setWeaklyReferenced_nolock方法修改weak新引用的对象的bit标志位

探索结构体

SideTables()

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;
//省略方法
//spinlock_t slock : 自旋锁,用于上锁/解锁 SideTable。
RefcountMap refcnts :用来存储OC对象的引用计数的 hash表(仅在未开启isa优化或在isa优化情况下isa_t的引用计数溢出时才会用到)。
weak_table_t weak_table : 存储对象弱引用指针的hash表。是OC中weak功能实现的核心数据结构。

};

补充知识

一个函数名之前加~,表示此函数是析构函数
关于c++的模版
c++模版
SideTables()

static objc::ExplicitInit<StripedMap<SideTable>> SideTablesMap;
//StripedMap<T> 是一个模板类,根据传递的实际参数决定其中 array 成员存储的元素类型。
// 能通过对象的地址,运算出 Hash 值,通过该 hash 值找到对应的 value 。

static StripedMap<SideTable>& SideTables() {
    return SideTablesMap.get();
}
这里的方法名是sideTables,返回值是StripedMap<SideTable>&
//这里面其实StripedMap里一共就一个PaddedT类型的数组,大小为StripeCount【64】
//可以大致理解为,该方法会返回一个StripedMap类型的数据结构,里面存储的类型是SideTable

来看看这个stripeMap

template<typename T>
class StripedMap {
//	如果是手机而不是模拟器
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif

    struct PaddedT {
        T value alignas(CacheLineSize);
    };

    PaddedT array[StripeCount];

    static unsigned int indexForPointer(const void *p) {
        uintptr_t addr = reinterpret_cast<uintptr_t>(p);
        return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
    }

 public:
    T& operator[] (const void *p) { 
        return array[indexForPointer(p)].value; 
    }
    const T& operator[] (const void *p) const { 
        return const_cast<StripedMap<T>>(this)[p]; 
    }
    //省略其他方法
    

StripedMap 是一个模板类, 内部维护一个大小为 StripeCount 的数组, 数组的成员为结构体 PaddedT, PaddedT 结构体只有一个成员 value, value 的类型是 T.
这里涉及到泛型相关知识, 结合起来理解, 就是说 SideTables() 返回的 StripedMap, 是一个 value 为 SideTable 的哈希桶(由于 SideTable 内部又在维护数组, 所以这是一个哈希桶结构), 哈希值由对象的地址计算得出.
什么是哈希桶呢?
有俩个关键字k1\k2,哈希函数得出同一个value。即f(k1)=f(k2),k1!=k2.这时候如果是普通的哈希表,那就放一个value,让下一个寻找空的地址,只要足够大,总能找到。
在这里插入图片描述

分离锁

分离锁
在这里插入图片描述

图1这样对一整个表加一把锁,是我们平时比较常见的。如果我一次写操作需要操作表中多个单元格的数据,比如第一次操作0、1、2位置的数据,第二次操作0、2、3位置的数据。像这种情况锁的粒度就是以整张表为单位的,才能保证数据的安全。
图2这样对表中的各个元素分别加一把锁就是我们说的分离锁。适用于表中元素相互独立,你对第一个元素做写操作的时候不需要影响到其他元素。
上文中所说的结构就是SideTables这个大的Hash表中每一个小单元格(SideTable)都带有一把锁。做写操作的时候(操作对象引用计数)单元格之间相互独立,互相没影响。所以降低了锁的粒度。

weak_table_t结构体

/**
 * The global weak references table. Stores object ids as keys,
 * and weak_entry_t structs as their values.
 */
struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};
//weak_entries: hash数组,用来存储弱引用对象的相关信息weak_entry_t
//这里不是指weak_entry_t是哈希表,是一个value是weak_entry_t的哈希数组weak_entries
num_entries:  hash数组中的元素个数
mask:hash数组长度-1,会参与hash计算。(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个)
max_hash_displacement:可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过该值)

weak_table_t是一个典型的hash结构。weak_entries是一个动态数组,用来存储weak_entry_t类型的元素,这些元素实际上就是OC对象的弱引用信息

weak_entry_t

weak_entry_t的结构是一个数组,其存储的元素是弱引用对象指针的指针, 通过操作指针的指针,就可以使得weak 引用的指针在对象析构后,指向nil。不过比较特殊的是,超出4个会用动态数组来管理
id __weak obj;
指向obj这个指针的指针。
这里我们用lldb打印验证它是个数组

id __weak obj;
    id __strong obj1 = [[NSObject alloc] init];
    for(int i = 0; i < 1000; i++) {
        [obj1 retain];
    }
    obj = obj1;
    NSLog(@"%p", &obj);
    id __weak obj3;
    obj3 = obj1;


 p *newTable
((anonymous namespace)::SideTable) $0 = {
  slock = {
    mLock = (_os_unfair_lock_opaque = 0)
  }
  refcnts = {
    Buckets = 0x000000010113e830
    NumEntries = 1
    NumTombstones = 0
    NumBuckets = 4
  }
  weak_table = {
    weak_entries = 0x0000000103808200
    num_entries = 1
    mask = 63
    max_hash_displacement = 0
  }
}
(lldb) p $0.weak_table
(weak_table_t) $1 = {
  weak_entries = 0x0000000103808200
  num_entries = 1
  mask = 63
  max_hash_displacement = 0
}
(lldb) p $1.weak_entries
(weak_entry_t *) $2 = 0x0000000103808200
(lldb) p *$2
(weak_entry_t) $3 = {
  referent = (value = 18446744069396535776)
   = {
     = {
      referrers = 0xffff800110400ae8
      out_of_line_ness = 0
      num_refs = 4611650835197199040
      mask = 0
      max_hash_displacement = 0
    }
     = {
      inline_referrers = {
        [0] = (value = 18446603340788796136)
        [1] = (value = 18446603340788796160)
        [2] = (value = 0)
        [3] = (value = 0)
      }
    }
  }
}
struct weak_entry_t {
    DisguisedPtr<objc_object> referent; //被引用的弱对象
     引用该对象的对象列表,联合。 引用个数小于4,用inline_referrers数组。 用个数大于4,用动态数组weak_referrer_t *referrers
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line_ness : 2;
            uintptr_t        num_refs : PTR_MINUS_2;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };

    bool out_of_line() {
        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
    }

    weak_entry_t& operator=(const weak_entry_t& other) {
        memcpy(this, &other, sizeof(other));
        return *this;
    }

    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
        : referent(newReferent) //构造方法,初始化了动态方法。
    {
        inline_referrers[0] = newReferrer;
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
            inline_referrers[i] = nil;
        }
    }
};

可以看到在weak_entry_t的结构定义中有联合体,在联合体的内部有定长数组inline_referrers[WEAK_INLINE_COUNT]和动态数组weak_referrer_t *referrers两种方式来存储弱引用对象的指针地址。通过out_of_line()这样一个函数方法来判断采用哪种存储方式。当弱引用该对象的指针数目小于等于WEAK_INLINE_COUNT时,使用定长数组。当超过WEAK_INLINE_COUNT时,会将定长数组中的元素转移到动态数组中,并之后都是用动态数组存储。

到这里我们已经清楚了弱引用表的结构是一个hash结构的表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象的地址)数组。那么接下来看看这个弱引用表是怎么维护这些数据的。

weak_register_no_lock方法添加弱引用

/// Adds an (object, weak pointer) pair to the weak table.
//向弱表添加一个(对象,弱指针)对。
id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;
// 如果referent为nil 或 referent 采用了TaggedPointer计数方式,直接返回,不做任何操作
    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

    // ensure that the referenced object is viable
    bool deallocating;
    // // 确保被引用的对象可用(没有在析构,同时应该支持weak引用)
    if (!referent->ISA()->hasCustomRR()) {
        deallocating = referent->rootIsDeallocating();
    }
    else {
        BOOL (*allowsWeakReference)(objc_object *, SEL) = 
            (BOOL(*)(objc_object *, SEL))
            object_getMethodImplementation((id)referent, 
                                           @selector(allowsWeakReference));
        if ((IMP)allowsWeakReference == _objc_msgForward) {
            return nil;
        }
        deallocating =
            ! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
    }
 // 正在析构的对象,不能够被弱引用
    if (deallocating) {
        if (crashIfDeallocating) {
            _objc_fatal("Cannot form weak reference to instance (%p) of "
                        "class %s. It is possible that this object was "
                        "over-released, or is in the process of deallocation.",
                        (void*)referent, object_getClassName((id)referent));
        } else {
            return nil;
        }
    }

    // now remember it and where it is being stored
    //  // 在 weak_table中找到referent对应的weak_entry,并将referrer加入到weak_entry中
    weak_entry_t *entry;
    //这里就比较糊了,referrer和referrent指的谁呢?这是调用
    //    weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);
     //这是原型
     //id  weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
     //也就是说,referent_id是新的被弱引用对象,referrer_id是__weak指针的地址。
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer); // 如果能找到weak_entry,则将referrer插入到weak_entry中
    } 
    else {  // 如果找不到,就新建一个
        weak_entry_t new_entry(referent, referrer);
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }

    // Do not set *referrer. objc_storeWeak() requires that the 
    // value not change.

    return referent_id;
}

weak_table:weak_table_t结构类型的全局的弱引用表。
referent_id:weak指针。
*referrer_id:weak指针地址。
crashIfDeallocating :若果被弱引用的对象正在析构,此时再弱引用该对象是否应该crash。
总结
如果referent为nil 或 referent 采用了TaggedPointer计数方式,直接返回,不做任何操作。
如果对象正在析构,则抛出异常。
如果对象不能被weak引用,直接返回nil。
如果对象没有正在析构且可以被weak引用,则调用weak_entry_for_referent方法根据弱引用对象的地址从弱引用表中找到对应的weak_entry,如果能够找到则调用append_referrer方法向其中插入weak指针地址。否则新建一个weak_entry。

weak_entry_for_referent取元素

//从返回看,这个函数的意思是取得一个weak_entries中的数组元素,也就是__weak指针的地址。

static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    ASSERT(referent);

    weak_entry_t *weak_entries = weak_table->weak_entries;

    if (!weak_entries) return nil;

    size_t begin = hash_pointer(referent) & weak_table->mask; // 这里通过 & weak_table->mask的位操作,来确保index不会越界
    size_t index = begin;
    size_t hash_displacement = 0;
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_table->weak_entries);
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    
    return &weak_table->weak_entries[index];
}

append_referrer添加元素

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    if (! entry->out_of_line()) { // 如果weak_entry 尚未使用动态数组
        // Try to insert inline.
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == nil) {
                entry->inline_referrers[i] = new_referrer;
                return;
            }
        }
  // 如果inline_referrers的位置已经存满了,则要转型为referrers,做动态数组。
         // Couldn't insert inline. Allocate out of line.
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
        // This constructed table is invalid, but grow_refs_and_insert
        // will fix it and rehash it.
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            new_referrers[i] = entry->inline_referrers[i];
        }
        entry->referrers = new_referrers;
        entry->num_refs = WEAK_INLINE_COUNT;
        entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
        entry->mask = WEAK_INLINE_COUNT-1;
        entry->max_hash_displacement = 0;
    }
// 对于动态数组的附加处理:
    ASSERT(entry->out_of_line()); //确定使用的是动态数组

    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {// 如果动态数组中元素个数大于或等于数组位置总空间的3/4,则扩展数组空间为当前长度的一倍
        return grow_refs_and_insert(entry, new_referrer); 
        //扩容并插入
    }
    /// 如果不需要扩容,直接插入到weak_entry中
    // 注意,weak_entry是一个哈希表,key:w_hash_pointer(new_referrer) value: new_referrer

    size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
    size_t index = begin; // 初始的hash index
    size_t hash_displacement = 0;  // 用于记录hash冲突的次数,也就是hash再位移的次数
    while (entry->referrers[index] != nil) {
        hash_displacement++;
        index = (index+1) & entry->mask;  index + 1, 移到下一个位置,再试一次能否插入。(这里要考虑到entry->mask取值,一定是:0x111, 0x1111, 0x11111, ... ,因为数组每次都是*2增长,即8, 16, 32,对应动态数组空间长度-1的mask,也就是前面的取值。)

        if (index == begin) bad_weak_table(entry); // // index == begin 意味着数组绕了一圈都没有找到合适位置,这时候一定是出了什么问题。
    }
    if (hash_displacement > entry->max_hash_displacement) {
        entry->max_hash_displacement = hash_displacement; // 记录最大的hash冲突次数, max_hash_displacement意味着: 我们尝试至多max_hash_displacement次,肯定能够找到object对应的hash位置
    }
    weak_referrer_t &ref = entry->referrers[index]; // // 将ref存入hash数组,同时,更新元素个数num_refs
    ref = new_referrer;
    entry->num_refs++;
}

(index+1) & entry->mask这句话是怎么确保不越界呢?
他是数组长度减1,如果是 8,则值为 0111,如果你取值到了8,1000,经过&之后,值又变回了0,所以不回越界。

weak_unregister_no_lock移除引用

void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;

    weak_entry_t *entry;

    if (!referent) return;

    if ((entry = weak_entry_for_referent(weak_table, referent))) { // 查找到referent所对应的weak_entry_t
        remove_referrer(entry, referrer);  // 在referent所对应的weak_entry_t的hash数组中,移除referrer
       
        // 移除元素之后, 要检查一下weak_entry_t的hash数组是否已经空了
        bool empty = true;
        if (entry->out_of_line()  &&  entry->num_refs != 0) {
            empty = false;
        }
        else {
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break;
                }
            }
        }

        if (empty) { // 如果weak_entry_t的hash数组已经空了,则需要将weak_entry_t从weak_table中移除
            weak_entry_remove(weak_table, entry);
        }
    }

用一个老例子。
一个大神写的
假设有80个学生需要咱们安排住宿,同时还要保证学生们的财产安全。应该怎么安排?
显然不会给80个学生分别安排80间宿舍,然后给每个宿舍的大门上加一把锁。那样太浪费资源了锁也挺贵的,太多的宿舍维护起来也很费力气。
我们一般的做法是把80个学生分配到10间宿舍里,每个宿舍住8个人。假设宿舍号分别是101、102 、… 110。然后再给他们分配床位,01号床、02号床等。然后给每个宿舍配一把锁来保护宿舍内同学的财产安全。为什么不只给整个宿舍楼上一把锁,每次有人进出的时候都把整个宿舍楼锁上?显然这样会造成宿舍楼大门口阻塞。
OK假如现在有人要找102号宿舍的2号床的人聊天。这个人会怎么做?
1、找到宿舍楼(SideTables)的宿管,跟他说自己要找10202(内存地址当做key)。
2、宿管带着他SideTables[10202]找到了102宿舍SideTable,然后把102的门一锁lock,在他访问102期间不再允许其他访客访问102了。(这样只是阻塞了102的8个兄弟的访问,而不会影响整栋宿舍楼的访问)
3、然后在宿舍里大喊一声:"2号床的兄弟在哪里?"table.refcnts.find(02)你就可以找到2号床的兄弟了。
4、等这个访客离开的时候会把房门的锁打开unlock,这样其他需要访问102的人就可以继续进来访问了。
SideTables == 宿舍楼;SideTable == 宿舍;RefcountMap里存放着具体的床位
苹果之所以需要创造SideTables的Hash冲突是为了把对象放到宿舍里管理,把锁的粒度缩小到一个宿舍SideTable。RefcountMap的工作是在找到宿舍以后帮助大家找到正确的床位的兄弟。

在这里插入图片描述

dealloc

当对象的引用计数为0时,底层会调用_objc_rootDealloc方法对对象进行释放,而在_objc_rootDealloc方法里面会调用rootDealloc方法。如下是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);
    }
}

1.首先判断对象是否是Tagged Pointer,如果是则直接返回。
2.如果对象是采用了优化的isa计数方式,且同时满足对象没有被weak引用!isa.weakly_referenced、没有关联对象!isa.has_assoc、没有自定义的C++析构方法!isa.has_cxx_dtor、没有用到SideTable来引用计数!isa.has_sidetable_rc则直接快速释放。
3.如果不能满足2中的条件,则会调用object_dispose方法

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_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

上面这一段代码很清晰,如果有自定义的C++析构方法,则调用C++析构函数。如果有关联对象,则移除关联对象并将其自身从Association Manager的map中移除。调用clearDeallocating方法清除对象的相关引用。

clearDeallocating

inline void 
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}

clearDeallocating中有两个分支,现实判断对象是否采用了优化isa引用计数,如果没有的话则需要清理对象存储在SideTable中的引用计数数据。如果对象采用了优化isa引用计数,则判断是都有使用SideTable的辅助引用计数(isa.has_sidetable_rc)或者有weak引用(isa.weakly_referenced),符合这两种情况中一种的,调用clearDeallocating_slow方法。

// Slow path of clearDeallocating() 
// for objects with nonpointer isa
// that were ever weakly referenced 
// or whose retain count ever overflowed to the side table.
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
    ASSERT(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables()[this];// 在全局的SideTables中,以this指针为key,找到对应的SideTable
    table.lock();
    if (isa.weakly_referenced) {// 如果obj被弱引用
        weak_clear_no_lock(&table.weak_table, (id)this);// 在SideTable的weak_table中对this进行清理工作
    }
    }
    if (isa.has_sidetable_rc) {/ /如果采用了SideTable做引用计数
        table.refcnts.erase(this);// 在SideTable的引用计数中移除this
    }
    table.unlock();
}
void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;

    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;
    }

    // zero out references
    weak_referrer_t *referrers;
    size_t count;
    
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i]; // 取出每个weak ptr的地址
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;   如果weak ptr确实weak引用了referent,则将weak ptr设置为nil,这也就是为什么weak 指针会自动设置为nil的原因
            }
            else if (*referrer) { // // 如果所存储的weak ptr没有weak 引用referent,这可能是由于runtime代码的逻辑错误引起的,报错
                _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();
            }
        }
    }
    
    weak_entry_remove(weak_table, entry); // 由于referent要被释放了,因此referent的weak_entry_t也要移除出weak_table
}
}

1、weak的原理在于底层维护了一张weak_table_t结构的hash表,key是所指对象的地址,value是weak指针的地址数组。
2、weak 关键字的作用是弱引用,所引用对象的计数器不会加1,并在引用对象被释放的时候自动被设置为 nil。
3、对象释放时,调用clearDeallocating函数根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

sideTbale里的其他内容

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;
 };

上面说到这个 RefcountMap refcnts是存储引用计数的,那它存储的原理是什么呢

typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,RefcountMapValuePurgeable> RefcountMap;

template <typename KeyT, typename ValueT,
          typename ValueInfoT = DenseMapValueInfo<ValueT>,
          typename KeyInfoT = DenseMapInfo<KeyT>,
          typename BucketT = detail::DenseMapPair<KeyT, ValueT>>
class DenseMap : public DenseMapBase<DenseMap<KeyT, ValueT, ValueInfoT, KeyInfoT, BucketT>,
                                     KeyT, ValueT, ValueInfoT, KeyInfoT, BucketT> {
  friend class DenseMapBase<DenseMap, KeyT, ValueT, ValueInfoT, KeyInfoT, BucketT>;

  // Lift some types from the dependent base class into this class for
  // simplicity of referring to them.
  using BaseT = DenseMapBase<DenseMap, KeyT, ValueT, ValueInfoT, KeyInfoT, BucketT>;

  BucketT *Buckets;
  unsigned NumEntries;
  unsigned NumTombstones;
  unsigned NumBuckets;

可以看到,它的类型定义十分复杂。这个map有三个参数,我们来看一下
第一个参数是第二次见到了,在weak_entry_t的结构体里就见到过.
这东西叫泛型,指定了数组、集合存储的类型是什么。这里就是指的objc_object *
DisguisedPtr<objc_object> :hash的key值,

size_t:Value

RefcountMapValuePurgeable:是否需要在value=0的时候自动释放响应的hash节点。此处是true。

关于size_t

size_t表达的就是对应对象的引用计数值,实际上它就是一个unsign long型的变量。

size_t的每个byte位代表的含义:
第一位表示的是对象是否有弱引用(weakly_referenced),0代表无,1代表有
第二位表示当前对象是否正在进行dealloc(deallocating)
其他位置存储对象实际的引用计数值。

所以我们在计算对象的引用计数值时,需要对这个值向右偏移2位。(如同源码中定义的宏:SIDE_TABLE_RC_SHIFT 2)

  BucketT *Buckets;
  unsigned NumEntries;
  unsigned NumTombstones;
  unsigned NumBuckets;

这里的原理从SideTables搬运
1.Buckets 指针管理一段连续内存空间, 也就是数组, 数组成员是 BucketT 类型的对象, 我们这里将 BucketT 对象称为桶(实际上这个数组才应该叫桶, 苹果把数组中的元素称为桶应该是为了形象一些, 而不是哈希桶中的桶的意思). 桶数组在申请空间后, 会进行初始化, 在所有位置上都放上空桶(桶的 key 为 EmptyKey 时是空桶), 之后对引用计数的操作, 都要依赖于桶.
桶的数据类型实际上是 std::pair, 类似于 swift 中的元祖类型, 就是将对象地址和对象的引用计数(这里的引用计数类似于 isa, 也是使用其中的几个 bit 来保存引用计数, 留出几个 bit 来做其它标记位)组合成一个数据类型.
2.NumEntries 记录数组中已使用的非空的桶的个数.
3.NumTombstones, Tombstone 直译为墓碑, 当一个对象的引用计数为0, 要从桶中取出时, 其所处的位置会被标记为 Tombstone. NumTombstones 就是数组中的墓碑的个数. 后面会介绍到墓碑的作用.
4.NumBuckets 桶的数量, 因为数组中始终都充满桶, 所以可以理解为数组大小.
通过阅读文章,这个墓碑的作用很简单,相当于是设立一个标志位,让hash算法继续寻找下一个空位置。
在这里插入图片描述
在这里插入图片描述
1.如图,很不巧,通过hash计算,abcd的下表都是0,那么他们线性探测后,存放位置如图1所属,此时c被释放了,如果设置的是空桶,显然,这里可以插入新元素,要给d、e增加引用计数就得写额外的代码,
2。如果此时初始化了一个新的对象 f, 根据哈希算法查找到下标为 2 的桶时发现桶中放置了墓碑, 此时会记录下来下标 2. 接下来继续哈希算法查找位置, 查找到空桶时, 就证明表中没有对象 f, 此时 f 使用记录好的下标 2 的桶而不是查找到的空桶, 就可以利用到已经释放的位置.

参考文章

weak实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值