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

文章目录

  • 查看汇编
    • 查看源码
    • 关键方法storeWeak
    • 总结用法
  • 探索结构体
    • SideTables()
    • 补充知识
        • 分离锁
    • weak_table_t结构体
    • weak_entry_t
    • weak_register_no_lock方法添加弱引用
      • weak_entry_for_referent取元素
      • append_referrer添加元素
    • weak_unregister_no_lock移除引用
  • dealloc
    • clearDeallocating
  • sideTbale里的其他内容
  • 参考文章

查看汇编

上次ARC的博客里涉及了__weak关键字,也简单了解了weak原理。
这篇博客来具体探究weak的底层原理。
iOS-runtime详解(二)weak底层原理_第1张图片
查看汇编代码
iOS-runtime详解(二)weak底层原理_第2张图片

查看源码

调用了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
        (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(oldTable, newTable);
//location应与oldObj保持一致
//
//location这个参数是__weak指针,如果旧对象不为空且它不指向旧对象,说明已经被其他线程修改
    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo(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(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(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> SideTablesMap;
//StripedMap 是一个模板类,根据传递的实际参数决定其中 array 成员存储的元素类型。
// 能通过对象的地址,运算出 Hash 值,通过该 hash 值找到对应的 value 。

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

来看看这个stripeMap

template
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(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>(this)[p]; 
    }
    //省略其他方法
    

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

分离锁

分离锁
iOS-runtime详解(二)weak底层原理_第4张图片

图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 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,size_t,RefcountMapValuePurgeable> RefcountMap;

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

  // Lift some types from the dependent base class into this class for
  // simplicity of referring to them.
  using BaseT = DenseMapBase;

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

可以看到,它的类型定义十分复杂。这个map有三个参数,我们来看一下
第一个参数是第二次见到了,在weak_entry_t的结构体里就见到过.
这东西叫泛型,指定了数组、集合存储的类型是什么。这里就是指的objc_object *
DisguisedPtr :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算法继续寻找下一个空位置。
在这里插入图片描述
iOS-runtime详解(二)weak底层原理_第5张图片
1.如图,很不巧,通过hash计算,abcd的下表都是0,那么他们线性探测后,存放位置如图1所属,此时c被释放了,如果设置的是空桶,显然,这里可以插入新元素,要给d、e增加引用计数就得写额外的代码,
2。如果此时初始化了一个新的对象 f, 根据哈希算法查找到下标为 2 的桶时发现桶中放置了墓碑, 此时会记录下来下标 2. 接下来继续哈希算法查找位置, 查找到空桶时, 就证明表中没有对象 f, 此时 f 使用记录好的下标 2 的桶而不是查找到的空桶, 就可以利用到已经释放的位置.

参考文章

weak实现

你可能感兴趣的:(iOS-runtime详解(二)weak底层原理)