weak

weak实现原理

  1. 初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。

  2. 添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。

  3. 释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

为了管理所有对象的引用计数和weak指针,创建了一个全局的SideTables。这是一个Hash表,里面装的是SideTable,用对象地址内存地址作为key进行散列。苹果内部将整个SideTables分为64分,所以就有64个SideTable。
SideTable结构如下

struct SideTable {
    //加锁,保证线程安全    
    spinlock_t slock;  
    /*一张记录引用计数器的散列表。
    */
    RefcountMap refcnts; 
    weak_table_t weak_table;

    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }

    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }

    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }

    // Address-ordered lock discipline for a pair of side tables.

    template<bool HaveOld, bool HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<bool HaveOld, bool HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

通过table.refcnts.find(this)找到对象的真正引用计数器ref,ref是size_t类型的,然后通过bit mask进行内容存储

// The order of these bits is important.
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0) //是否有弱指针指向这个对象,1代表有
#define SIDE_TABLE_DEALLOCATING      (1UL<<1)  // 是否正在被销毁,1代表是
#define SIDE_TABLE_RC_ONE            (1UL<<2)  //真正的引用计数
#define SIDE_TABLE_RC_PINNED         (1UL<<(WORD_BITS-1)) //最大的引用计数次数

然后我们看看SideTabel中的weak_table_t weak_table,它是如下结构

struct weak_table_t {
    weak_entry_t *weak_entries; //一个放置weak_entry_t的数组
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};

//weak_entry_t的数据结构
struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    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中,我们看到一个类型为DisguisedPtr,名为referent的指针,这里的被指向对象的地址,存储的是我看到对这个变量苹果的注释如下

//DisguisedPtr<T> acts like pointer type T*, except the 
// stored value is disguised to hide it from tools like `leaks`.

说是对指针的一种封装,目的是防止泄露。
接下来是weak_referrer_t, ,存储的是弱引用对象的地址。

// The address of a __weak variable.
// These pointers are stored disguised so memory analysis tools
// don't see lots of interior pointers from the weak table into objects.
typedef DisguisedPtr<objc_object *> weak_referrer_t;

至于weak_referrer_t inline_referrers 当弱引用对象不多于4个时候,实际弱引用对象的地址存在这里面的,多余4个则存referrers里。

介绍完基本构成之后我们再来看看retain,release,retainCount这些操作是怎么实现的。(这里不讨论alloc 是因为alloc只涉及到内存的分配和isa的初始化,与上文讲的无关)
retain:

//下面是对对象进行retain,可以看到是如何实现引用计数加1的
id objc_object::sidetable_retain()
{
//isa指针不能是taggedPointer(若是,就会在isa中进行引用计数的存储)
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];

    table.lock();
     //从散列表中获取这个size_t
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        //当小于最大引用计数,引用计数+1 (这里是二进制加法)
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();

    return (id)this;
}

reaintCount:

uintptr_t objc_object::sidetable_retainCount()
{
    SideTable& table = SideTables()[this];

    //引用计数初始化为1
    size_t refcnt_result = 1;

    table.lock();
  //迭代获取RefcountMap,key为objc_object也就是内存地址,value为引用计数
    RefcountMap::iterator it = table.refcnts.find(this);
  //当找到RefcountMap
    if (it != table.refcnts.end()) {
        // it->second 是取的value也就是引用计数,然后右移两位再+1,这是因为低两位记录了其他状态,见上文
        refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
    }
    table.unlock();
  //所以获取的引用计数>=1
    return refcnt_result;
}

release:

uintptr_t objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];

    bool do_dealloc = false;

    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) {
    //假如迭代器没有找到RefcountMap,结果标记为false,size_t标记为释放中
        do_dealloc = true;
        table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
    } else if (it->second < SIDE_TABLE_DEALLOCATING) {
        // 当引用计数为0且size_t没有被标记为释放中时,进行标记
        do_dealloc = true;
        it->second |= SIDE_TABLE_DEALLOCATING;
    } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
      //没有超过最大引用计数时,且引用计数不为0时引用计数减1(二进制减法)
        it->second -= SIDE_TABLE_RC_ONE;
    }
    table.unlock();
    if (do_dealloc  &&  performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return do_dealloc;
}

assign和weak的区别

assign 一般用在基本数据类型上


@property (nonatomic, assign) Test *test;

Test *__unsafe_unretained _test(真实类型)

速度: unsafe_unretained > weak

  • __unsafe_unretained的特点:

    • 不是强引用, 不能保住OC对象的命

    • 如果引用的OC对象销毁了, 指针并不会被自动清空, 依然指向销毁的对象(很容易产生野指针错误: EXC_BAD_ACCESS)

weak一般用在代理对象上面, 或者用来解决循环强引用的问题


@property (nonatomic, weak) Test *test;

Test * _Nullable __weak test(真实类型)

  • __weak的特点:

    • 不是强引用, 不能保住OC对象的命

    • 如果引用的OC对象销毁了, 指针会被自动清空(变为nil), 不再指向销毁的对象(不会产生野指针错误)

results matching ""

    No results matching ""