收藏ios

iOS weak源码详解

2019-10-05  本文已影响0人  萨缪

准备工作

数据模型

SideTables()

static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}

StripedMap

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);
        //enum { CacheLineSize = 64 };
    };

    PaddedT array[StripeCount];
    //enum { StripeCount = 64 };

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

补充知识:哈希桶

SideTable

struct SideTable {
    spinlock_t slock;   //自旋锁
    RefcountMap refcnts;    //存放引用计数
    weak_table_t weak_table;   //weak_table是一个哈希
};

spinlock_t slock【自旋锁】

补充知识:锁

RefcountMap refcnts【存放引用计数】

引用计数存在RefcountMap结构里,也就是上文介绍哈希桶的时候提到的拉链
这也是个hash结构,根据对象地址存储引用计数

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

//DenseMap
template<typename KeyT, typename ValueT,
         bool ZeroValuesArePurgeable = false, 
         typename KeyInfoT = DenseMapInfo<KeyT> >
class DenseMap
    : public DenseMapBase<DenseMap<KeyT, ValueT, ZeroValuesArePurgeable, KeyInfoT>,
                          KeyT, ValueT, KeyInfoT, ZeroValuesArePurgeable> {
  typedef DenseMapBase<DenseMap, KeyT, ValueT, KeyInfoT, ZeroValuesArePurgeable> BaseT;
  //ZeroValuesArePurgeable:ZeroValuesArePurgeable 默认值是 false, 但 RefcountMap 指定其初始化为 true. 这个成员标记是否可以使用值为 0 (引用计数为 1) 的桶.

                              
  typedef typename BaseT::BucketT BucketT;
  friend class DenseMapBase<DenseMap, KeyT, ValueT, KeyInfoT, ZeroValuesArePurgeable>;

  BucketT *Buckets;
  //Buckets 指针管理一段连续内存空间, 也就是数组, 数组成员是 BucketT 类型的对象桶的 key 为 EmptyKey 时是空桶
  //typedef std::pair<KeyT, ValueT> BucketT;
  //桶的数据类型std::pair,将对象地址和对象的引用计数(这里的引用计数类似于 isa, 也是使用其中的几个 bit 来保存引用计数, 留出几个 bit 来做其它标记位)组合成一个数据类型.
  unsigned NumEntries;
  //NumEntries 记录数组中已使用的非空的桶的个数.
  unsigned NumTombstones;
  //当一个对象的引用计数为0, 要从桶中取出时, 其所处的位置会被标记为 TombstoneNumTombstones 就是数组中的墓碑的个数. 后面会介绍到墓碑的作用
  unsigned NumBuckets;
  //NumBuckets 桶的数量, 因为数组中始终都充满桶, 所以可以理解为数组大小.
}

DisguisedPtr

DenseMap

补充知识:LLVM
哈希实现

1479888-47765d58b3dbe9b8

1479888-ea61e198d6f13201 图片.png

weak_table_t weak_table

/**
 * 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; //管理所有指向某对象的weak指针,也是一个hash
    size_t    num_entries;  //数组中已占用位置的个数
    uintptr_t mask;  //数组下标最大值(即数组大小 -1)
    uintptr_t max_hash_displacement;  //最大哈希偏移值
};

weak_entry_t *weak_entries【存放weak指针】

struct weak_entry_t {
    DisguisedPtr<objc_object> referent; //被指对象的地址。前面循环遍历查找的时候就是判断目标地址是否和他相等。
    union {
        struct {
            weak_referrer_t *referrers; //可变数组,里面保存着所有指向这个对象的弱引用的地址。当这个对象被释放的时候,referrers里的所有指针都会被设置成nil。
            //指向 referent 对象的 weak 指针数组
            uintptr_t        out_of_line_ness : 2; //这里标记是否超过内联边界, 下面会提到
            uintptr_t        num_refs : PTR_MINUS_2; //数组中已占用的大小
            uintptr_t        mask; //数组下标最大值(数组大小 - 1)
            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]; //只有4个元素的数组,默认情况下用它来存储弱引用的指针。当大于4个的时候使用referrers来存储指针。
            //当指向这个对象的weak指针不超过4个,则直接使用数组inline_referrers,省去hash
        };
    };

结构总结

图片.png

SideTables以及SideTable的关系

weak相关的方法

objc_initWeak(id *location, id newObj)

id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

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

storeWeak(id *location, objc_object *newObj)

    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

    // Clean up old value, if any.
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // 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.
    }
    return (id)newObj;

weak_unregister_no_lock

//从原有的表中先删除这个 weak 指针注销已注册的弱引用。当引用者的存储即将消失,但引用还没有死。(否则,稍后归零引用将是内存访问错误。)如果引用/引用不是当前活动的弱引用,则不执行任何操作。引用不为零。
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
{
    objc_object *referent = (objc_object *)referent_id; //weak 指针指向的对象
    objc_object **referrer = (objc_object **)referrer_id;  //referrer_id是 weak 指针, 操作时需要用到这个指针的地址

    weak_entry_t *entry;

    if (!referent) return;

    if ((entry = weak_entry_for_referent(weak_table, referent))) {  //查找 referent 对象对应的 entry
        remove_referrer(entry, referrer);  //从 referent 对应的 entry 中删除地址为 referrer 的 weak 指针
        bool empty = true;
        if (entry->out_of_line()  &&  entry->num_refs != 0) { //如果 entry 中的数组容量大于 4 并且数组中还有元素
            empty = false; //entry 非空
        }
        else {
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) { //否则循环查找 entry 数组, 如果 4 个位置中有一个非空
                    empty = false;  //entry 非空
                    break;
                }
            }
        }
        if (empty) {
            weak_entry_remove(weak_table, entry); //从 weak_table 中移除该条 entry
        }
    }
}

weak_register_no_lock
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;
// now remember it and where it is being stored
weak_entry_t *entry; //如果 weak_table 有对应的 entry
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer); //将 weak 指针存入对应的 entry 中
}
else {
weak_entry_t new_entry(referent, referrer); //创建新的 entry
weak_grow_maybe(weak_table); //查看是否需要调整 weak_table 中 weak_entries 数组大小
weak_entry_insert(weak_table, &new_entry); //将新的 entry 插入到 weak_table 中
}
return referent_id;
}
这里如果该地址有对应的entry,在里面存入指针;如果没有entry,就新建entry,在里面插入weak指针,并插入weak_table

weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
该方法通过对象的地址,获取到该weak_table中的entry【保存着指向某对象的所有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;
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];

}
hash_pointer也是使用指针地址,映射到一个索引。&weak_table->mask这个操作是?这个mask实际值是表的size-1,而size是2的n次方进行扩张的,所以mask的形式就1111 1111 1111这种,索引和mask位与之后的值必定就落在了[0, size]范围内。也就是说,先是通过hash_pointer对地址进行hash映射,得到下标,接下来通过位遮蔽,保证这个映射是在范围内的,不会超出范围
index = (index+1) & weak_table->mask;是在遇到哈希冲突的时候,就一直往下找下一个位置

下面我们看下append_referrer的实现

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    if (! entry->out_of_line()) { //如果数组大小没超过 4
        // 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; //把新的 weak 指针插入到空位置
                return;
            }
        }
        //也就是说走到这就是刚好之前有四个引用,现在添加第五个
        //数组中的 4 个位置都非空, 就要调整策略使用 referrers 了
        //从这里开始, 这一段是把 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)); //还是开辟 4 个 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]; //将 inline_referrers 中的值赋值给 referrers
        }
        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) {
        return grow_refs_and_insert(entry, new_referrer);
    }

    //开始哈希算法
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
    size_t index = begin; //使用哈希算法计算到一个起始下标
    size_t hash_displacement = 0; //哈希偏移次数
    while (entry->referrers[index] != nil) {  //循环找空位置
        hash_displacement++;
        index = (index+1) & entry->mask;
        if (index == begin) bad_weak_table(entry);
    }
    //这里记录下移位的最大值, 那么数组里的任何一个数据, 存储时的移位次数都不大于这个值
    //可以提升查找时的效率, 如果移位次数超过了这个值都没有找到, 就证明要查找的项不在数组中
    if (hash_displacement > entry->max_hash_displacement) {
        entry->max_hash_displacement = hash_displacement;
    }
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
}

赋值操作

weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
entry->referrers[index] = new_referrer;
entry->num_refs++;
#include <iostream>
using namespace std;

struct Padd {
    int a;
    float b;
}bucket[10];

int main(int argc, const char * argv[]) {
    // insert code here...
    std::cout << "Hello, World!\n";
    int j = 0;
    float k = 16;
    for (Padd &i : bucket) {
        i.a = j++;
        i.b = k++;
    }
    for (Padd &i : bucket) {
        cout << i.a << "XXX" << i.b << endl;
    }

    Padd *testPadd = (Padd *)malloc(sizeof(Padd));
    testPadd->a = 99;
    testPadd->b = 109;
    Padd &ref = bucket[4];
    ref = *testPadd;
    printf("%p\n", &bucket[4]);
    printf("%p\n", &ref);

    for (Padd &i : bucket) {
        cout << i.a << "XXX" << i.b << endl;
    }

    return 0;
}

dealloc后将weak指针置nil【weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 】

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) {
        return;
    }
    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];
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;
            }
        }
    }

    weak_entry_remove(weak_table, entry);
}

方法总结

2664540-255e050c9fafa044

后续更新

对于weak对象的访问:objc_loadWeakRetained

id  __weak obj1 = obj0;
NSLog(@"class = %@",[obj1 class]);

id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"class = %@",[tmp class]);//实际访问的是注册到自动释放池的对象
//实验代码
NSObject *obj0 = [[NSObject alloc] init];
NSObject *obj1 __weak = obj0;
printf("retain count = %ld\n",CFGetRetainCount((__bridge CFTypeRef)(obj0))); //结果为1
printf("retain count = %ld\n",CFGetRetainCount((__bridge CFTypeRef)(obj1)));    //断点打在这里 结果为2
id
objc_loadWeakRetained(id *location)
{
    id obj;
    id result;
    Class cls;

    SideTable *table;

 retry:
    // fixme std::atomic this load
    obj = *location;
    if (!obj) return nil;
    if (obj->isTaggedPointer()) return obj;

    table = &SideTables()[obj];

    table->lock();
    if (*location != obj) {
        table->unlock();
        goto retry;
    }
    result = obj;

    cls = obj->ISA();
    if (! cls->hasCustomRR()) {
        // Fast case. We know +initialize is complete because
        // default-RR can never be set before then.
        assert(cls->isInitialized());
        if (! obj->rootTryRetain()) {   //rootTryRetain执行了retain操作
            result = nil;
        }
    }    
    table->unlock();
    return result;
}

isa_t中存放的引用计数

inline uintptr_t 
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    if (bits.nonpointer) {
        uintptr_t rc = 1 + bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}

为什么weak_entries中referrers要使用hash?

我们来理一理思路,在weak_entries中,已经有referent存储了被指对象的地址,referrers中存放的是所有指向该对象的weak指针的地址
而我们使用了hash的方式存储,我们将weak指针的地址hash出一个index来存放weak指针的地址
有没有觉得怪怪的?一般来说我们用key【一般是对象地址】哈希出一个index作为存放地址,存放的是对象本身,也就是value
但在这里,等于就是用key存key了。因此我之前以为这里没必要hash,因为假如我们希望查询到该weak指针的地址,我们得拿weak指针的地址去找。。。
但其实这里还是因为没有很好的理解他的意义,这张表不是为了查找weak指针的地址而存在的,而是要记录指向某一对象的所有weak指针而存在的,其存在的意义就是在记录上有本身
我们查找某一weak指针往往基本上就是为了把记录这个东西
在这张表上删除,我们的目的在于记录本身,而不是其value本身,以上。

上一篇下一篇

猜你喜欢

热点阅读