iOS底层-weak探索

1,196 阅读18分钟

引言

在开发中我们经常会用到weak关键字,用的比较多的是声明弱引用属性和__weak声明弱引用指针

@property (nonatomic, weak) id<Delegate>delegate
__weak typeof(self) weakSelf = self;
  • 在对象释放时如果有强引用指针指向对象,那么对象就不会被释放,如果两个对象相互持有就会造成内存泄漏。
  • 在对象释放时如果有弱引用指针指向对象,系统会先将弱指针置为空再释放对象,不会出现内存泄漏

今天我们探索一下weak的底层实现

int main(int argc, char * argv[]) {
    @autoreleasepool {
        JPerson *person1 = [JPerson new];
        JPerson *person2 = [JPerson new];
        __weak id wPerson = person1; //在这一行打断点
        wPerson = person2;
    }
    return 0;
}

image.png

  • objc_initWeak声明弱指针,对应__weak id wPerson = person1
  • objc_storeWeak给弱指针赋值,对应wPerson = person2
  • objc_destroyWeak出了作用域若指针释放
  • objc_storeStrong出了作用域强指针释放,有两个所以调用两次

打开objc4-781.2源码搜索objc_initWeakobjc_initWeak方法内部调用了storeWeakstoreWeak内部涉及到了SideTables()结构

static objc::ExplicitInit<StripedMap<SideTable>> SideTablesMap;

static StripedMap<SideTable>& SideTables() {
    return SideTablesMap.get();
}

数据结构

StripedMap

enum { CacheLineSize = 64 };

// StripedMap<T> is a map of void* -> T, sized appropriately 
// StripedMap<T>结构是一个 void*指针指向T类型的一种表结构
// for cache-friendly lock striping. 
// For example, this may be used as StripedMap<spinlock_t>
// or as StripedMap<SomeStruct> where SomeStruct stores a spin lock.
// StripedMap<SomeStruct>这种结构SomeStruct要包含一个spin lock

template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 }; //iPhone真机
#else
    enum { StripeCount = 64 }; //iPhone模拟器或者其他
#endif
    //PaddedT结构体中存储了一个T类型的值,以64字节对齐
    struct PaddedT {
        T value alignas(CacheLineSize);
    };

    // PaddedT数组,长度为StripeCount
    PaddedT array[StripeCount];

    // StripedMap是一个哈希表,这里是哈希算法,根据对象地址计算对应下标
    static unsigned int indexForPointer(const void *p) {
        uintptr_t addr = reinterpret_cast<uintptr_t>(p);
        return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
    }

 public:
     // C++语法中的符号重载
    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<T>是一个固定长度的哈希表,哈希表中存储了结构体PaddedTPaddedT中存储了范性T的值,这里的范性T对应的具体类型为SideTable

SideTable

// Template parameters.
enum HaveOld { DontHaveOld = false, DoHaveOld = true };
enum HaveNew { DontHaveNew = false, DoHaveNew = true };

struct SideTable {
    spinlock_t slock; //这里有一个自旋锁spinlock_t
    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(); }
    void forceReset() { slock.forceReset(); }

    // Address-ordered lock discipline for a pair of side tables.
    template<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

weak_table_t

/**
 * The global weak references table. Stores object ids as keys,
 * and weak_entry_t structs as their values.
 * 全局弱引用表,以对象指针为key,以weak_entry_t为value
 */
struct weak_table_t {
    weak_entry_t *weak_entries; //存储weak_entry_t数组
    size_t    num_entries; //当前存储weak_entry_t的个数
    uintptr_t mask; //最大容量-1的值
    uintptr_t max_hash_displacement;
};

weak_entry_t

这里的注释很多,耐心看完,从注释里面往往能得到很有用的信息

/*
The weak table is a hash table governed by a single spin lock.
weak table是一个哈希表,被单个自旋锁管理

An allocated blob of memory, most often an object, but under GC any such 
allocation, may have its address stored in a __weak marked storage location 
through use of compiler generated write-barriers or hand coded uses of the 
register weak primitive. Associated with the registration can be a callback 
block for the case when one of the allocated chunks of memory is reclaimed. 
The table is hashed on the address of the allocated memory.  When __weak 
marked memory changes its reference, we count on the fact that we can still 
see its previous reference.

So, in the hash table, indexed by the weakly referenced item, is a list of 
all locations where this address is currently being stored.
 
For ARC, we also keep track of whether an arbitrary object is being 
deallocated by briefly placing it in the table just prior to invoking 
dealloc, and removing it via objc_clear_deallocating just prior to memory 
reclamation.
*/

// 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.
// 之所以使用DisguisedPtr对指针进行伪装是防止内存检测工具误以为内存泄漏
typedef DisguisedPtr<objc_object *> weak_referrer_t;


// 联合体中num_refs所占二进制位数
#if __LP64__
#define PTR_MINUS_2 62 
#else
#define PTR_MINUS_2 30
#endif

/**
 * The internal structure stored in the weak references table. 
 * It maintains and stores
 * a hash set of weak references pointing to an object.
 * If out_of_line_ness != REFERRERS_OUT_OF_LINE then the set
 * is instead a small inline array.
 * 默认使用数组inline_referrers[4]存储,当
 */
 
#define WEAK_INLINE_COUNT 4

// out_of_line_ness field overlaps with the low two bits of inline_referrers[1].
// inline_referrers[1] is a DisguisedPtr of a pointer-aligned address.
// The low two bits of a pointer-aligned DisguisedPtr will always be 0b00
// (disguised nil or 0x80..00) or 0b11 (any other address).
// Therefore out_of_line_ness == 0b10 is used to mark the out-of-line state.
#define REFERRERS_OUT_OF_LINE 2

struct weak_entry_t {
    // 伪装之后的对象地址
    DisguisedPtr<objc_object> referent;
    
    // 这是一个联合体referrers和inline_referrers互斥,默认使用inline_referrers[4]存储
    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;
        }
    }
};

image.png

相关方法

objc_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.  location是弱引用指针的地址
 * @param newObj Object ptr.  newObj是对象指针
 */
id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }
    //注意这里的参数DontHaveOld、DoHaveNew没有旧值有新的值
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

objc_storeWeak

/** 
 * This function stores a new value into a __weak variable. It would
 * be used anywhere a __weak variable is the target of an assignment.
 * 这个方法是对一个已存在的弱引用指针重新赋值的操作
 * 
 * @param location The address of the weak pointer itself
 * location是弱引用指针自己的地址
 * @param newObj The new object this weak ptr should now point to
 * newObj是新对象的指针
 * 
 * @return \e *newObj*
 */
id
objc_storeWeak(id *location, id newObj)
{
    //注意这里的参数DoHaveOld、DoHaveNew有旧值也有新的值
    return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object *)newObj);
}

objc_destroyWeak

/** 
 * Destroys the relationship between a weak pointer
 * and the object it is referencing in the internal weak
 * table. If the weak pointer is not referencing anything, 
 * there is no need to edit the weak table. 
 * 这个函数是当弱引用指针指向内容为空的时候解除弱引用指针和对象的关联关系
 *
 * This function IS NOT thread-safe with respect to concurrent 
 * modifications to the weak variable. (Concurrent weak clear is safe.)
 * 
 * @param location The weak pointer address. 
 */
void
objc_destroyWeak(id *location)
{
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);
}

我们发现无论是objc_initWeakobjc_storeWeak还是objc_destroyWeak最终都调用了storeWeak,那么storeWeak很可能是我们研究的重点

storeWeak

// Update a weak variable. 这个方法是用来更新弱变量的
// If HaveOld is true, the variable has an existing value 
//   that needs to be cleaned up. This value might be nil.
// 如果HaveOld为true那么需要先把旧值清理,这个值可能是空
// If HaveNew is true, there is a new value that needs to be 
//   assigned into the variable. This value might be nil.
// 如果HaveNew为true那么需要把新值存储到弱变量处,这个值可能是空
// If CrashIfDeallocating is true, the process is halted if newObj is 
//   deallocating or newObj's class does not support weak references. 
// 如果CrashIfDeallocating为true,如果newObj正在释放或者这个类不支持弱引用指针进程就会崩溃
//   If CrashIfDeallocating is false, nil is stored instead.
// 如果CrashIfDeallocating为false,以上情况出现的时候会存储空值而不会崩溃
enum CrashIfDeallocating {
    DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};

template <HaveOld haveOld, HaveNew haveNew,
          CrashIfDeallocating crashIfDeallocating>
          
//从上面objc_initWeak和objc_storeWeak方法中看到location是弱引用指针的地址,newObj是对象的指针
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) {
        // location表示弱引用指针的地址,*location是对象的指针,oldObj就是旧对象的指针
        oldObj = *location;
        // 通过旧对象指针获取对应的sideTable
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }

    if (haveNew) {
        // 如果有新值,直接通过新值的指针获取对应的sideTable
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    // 将两个sideTable锁住
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    //如果弱引用指针*location没有指向旧对象,这应该是在此过程中发生了改变
    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.
    // 防止出现没有初始化的情况
    // 如果在+initialize方法中出现弱引用相关操作会出现递归,通过previouslyInitializedClass打破递归
    if (haveNew  &&  newObj) {
        获取newObj的Class
        Class cls = newObj->getIsa();
        // 第一次:如果cls不为空并且对象没有初始化
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            // 解锁,对象初始化
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            class_initialize(cls, (id)newObj);

            // If this class is finished with +initialize then we're good.
            // 如果class完成了+initialize的调用那么你好我好大家好
            // 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.
            // 如果正在执行+initialize那么我们继续执行,过一会再来检查
            // Instead set previouslyInitializedClass to recognize it on retry.
            // previouslyInitializedClass = cls之后cls != previouslyInitializedClass就不成立了,
            // 所以下次就不进来了,保证class_initialize只被执行一次
            previouslyInitializedClass = cls;
            goto retry;
        }
    }

    // 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()) {
            // 更新新值isa,标记存在弱引用信息
            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;
}

weak_register_no_lock

/** 
 * Registers a new (object, weak pointer) pair. Creates a new weak
 * object entry if it does not exist.
 * 如果没有对应的entry就创建一个,注册一个object和弱引用指针对
 *
 * @param weak_table The global weak table.
 * @param referent The object pointed to by the weak reference.
 * @param referrer The weak pointer address. 弱引用指针的地址
 */
id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
{
    // referent对象指针 referrer弱引用指针的地址
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;

    // taggedPointer不需要使用引用计数管理,更不需要使用weak_table进行管理
    if (!referent  ||  referent->isTaggedPointer()) return referent_id;
    // ensure that the referenced object is viable 确保引用对象是合法的
    bool deallocating;
    // 这里是判断当前对象是否正在释放,细节待后面研究明白了再来补充😄
    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;
        }
    }

    // 截止到这里我们能保证对象有效,不为空、不是taggedPointer对象、不是正在释放的对象
    // now remember it and where it is being stored
    weak_entry_t *entry;
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        // 如果根据对象地址找到了已经存在的entry,那么存储referrer
        append_referrer(entry, referrer);
    } 
    else {
        // 如果没找到对应的entry,需要新创建
        weak_entry_t new_entry(referent, referrer);
        // 检查weak_table的weak_entries是不是需要扩容,需要就扩容
        weak_grow_maybe(weak_table);
        // 将新创建的entry存储到weak_table的weak_entries中
        weak_entry_insert(weak_table, &new_entry);
    }

    // Do not set *referrer. objc_storeWeak() requires that the 
    // value not change.
    return referent_id;
}

weak_entry_for_referent

/** 
 * Return the weak reference table entry for the given referent. 
 * If there is no entry for referent, return NULL. 
 * 通过对象指针在weak_table中查找对应的table entry,查找不到就返回空
 * Performs a lookup.
 *
 * @param weak_table 
 * @param referent The object. Must not be nil.
 * 
 * @return The table of weak referrers to this object. 
 */
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;

    // 这里通过对象指针进行哈希计算得到对应在weak_entries中的位置,具体细节还不理解😄
    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;
        }
    }

    // 经过这一系列哈希运算反正就是得到了index,如果不存在上面👆已经返回nil了
    return &weak_table->weak_entries[index];
}

append_referrer

/** 
 * Add the given referrer to set of weak pointers in this entry.
 * Does not perform duplicate checking (b/c weak pointers are never
 * added to a set twice). 
 * 将弱引用指针的地址存储在对应的entry中,不需要查重,因为一个referrer不会被重复添加(就是这么自信)
 *
 * @param entry The entry holding the set of weak pointers. 
 * @param new_referrer The new weak pointer to be added.
 */
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    // 如果inline_referrers还没存满
    if (! entry->out_of_line()) {
        // Try to insert inline.
        // 在inline_referrers中找到一个空位置插入
        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满了,那么要存储在new_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.
        // 将inline_referrers中的值存储到new_referrers中
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            new_referrers[i] = entry->inline_referrers[i];
        }
        // entry的referrers指针指向新开辟的空间
        entry->referrers = new_referrers;
        // num_refs存储当前referrers存储了多少个值
        entry->num_refs = WEAK_INLINE_COUNT;
        // 这里赋值成功之后out_of_line()函数返回true,标记存储在referrers中而不是inline_referrers中
        entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
        // mask存储referrers容量-1的值
        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) {
        // 去扩容 并且会调用append_referrer函数存储new_referrer
        return grow_refs_and_insert(entry, new_referrer);
    }
    
    // 不需要扩容,找到对应的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;
    }
    
    // 存储new_referrer并且修改数量
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
}

grow_refs_and_insert

/** 
 * Grow the entry's hash table of referrers. Rehashes each
 * of the referrers.
 * 这个函数要求必须存储在weak_entries里面了,这是要对weak_entries扩容
 * 
 * @param entry Weak pointer hash set for a particular object.
 */
__attribute__((noinline, used))
static void grow_refs_and_insert(weak_entry_t *entry, 
                                 objc_object **new_referrer)
{
    // 这个断言已经要求必须存储在weak_entries里面了
    ASSERT(entry->out_of_line());

    // 老的weak_entries大小
    size_t old_size = TABLE_SIZE(entry);
    // weak_entries扩容之后的大小
    size_t new_size = old_size ? old_size * 2 : 8;
    // 已经存储的数量
    size_t num_refs = entry->num_refs;
    weak_referrer_t *old_refs = entry->referrers;
    entry->mask = new_size - 1;
    entry->referrers = (weak_referrer_t *)
        calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));
    entry->num_refs = 0;
    entry->max_hash_displacement = 0;

    // 将老的referrers存储到新的referrers中
    for (size_t i = 0; i < old_size && num_refs > 0; i++) {
        if (old_refs[i] != nil) {
            append_referrer(entry, old_refs[i]);
            num_refs--;
        }
    }
    // Insert 存储新来的new_referrer
    append_referrer(entry, new_referrer);
    
    // 将老的referrers回收
    if (old_refs) free(old_refs);
}

weak_grow_maybe

这个函数用来检查weak_tableweak_entries是不是需要扩容

// Grow the given zone's table of weak references if it is full.
static void weak_grow_maybe(weak_table_t *weak_table)
{
    size_t old_size = TABLE_SIZE(weak_table);

    // Grow if at least 3/4 full.
    if (weak_table->num_entries >= old_size * 3 / 4) {
        weak_resize(weak_table, old_size ? old_size*2 : 64);
    }
}

weak_resize

这个函数很简单,就是将weak_tableweak_entries扩容并且将老的weak_entries中的值存储到新的weak_entries中来

static void weak_resize(weak_table_t *weak_table, size_t new_size)
{
    size_t old_size = TABLE_SIZE(weak_table);

    weak_entry_t *old_entries = weak_table->weak_entries;
    weak_entry_t *new_entries = (weak_entry_t *)
        calloc(new_size, sizeof(weak_entry_t));

    weak_table->mask = new_size - 1;
    weak_table->weak_entries = new_entries;
    weak_table->max_hash_displacement = 0;
    weak_table->num_entries = 0;  // restored by weak_entry_insert below

    if (old_entries) {
        weak_entry_t *entry;
        weak_entry_t *end = old_entries + old_size;
        for (entry = old_entries; entry < end; entry++) {
            if (entry->referent) {
                weak_entry_insert(weak_table, entry);
            }
        }
        free(old_entries);
    }
}

weak_entry_insert

/** 
 * Add new_entry to the object's table of weak references.
 * Does not check whether the referent is already in the table.
 * 将新创建的weak_entry存储起来,不需要查重
 */
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
    weak_entry_t *weak_entries = weak_table->weak_entries;
    ASSERT(weak_entries != nil);

    // 根据entry中的对象地址referent,经过哈希运算得到一个index
    size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    while (weak_entries[index].referent != nil) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_entries);
        hash_displacement++;
    }

    // 将entry存储到对应位置
    weak_entries[index] = *new_entry;
    // 数量+1
    weak_table->num_entries++;

    if (hash_displacement > weak_table->max_hash_displacement) {
        weak_table->max_hash_displacement = hash_displacement;
    }
}

weak_unregister_no_lock

/** 
 * Unregister an already-registered weak reference.
 * 解注册一个已经注册的弱引用
 * This is used when referrer's storage is about to go away, but referent
 * isn't dead yet. (Otherwise, zeroing referrer later would be a
 * bad memory access.) 适用于弱引用指针出了作用域要被置空但是对象还没有被释放
 * Does nothing if referent/referrer is not a currently active weak reference.
 * Does not zero referrer.
 * 
 * FIXME currently requires old referent value to be passed in (lame)
 * FIXME unregistration should be automatic if referrer is collected
 * 
 * @param weak_table The global weak table.
 * @param referent The object.
 * @param referrer The weak reference.
 */
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
{
    // referent为对象指针
    objc_object *referent = (objc_object *)referent_id;
    // referrer为弱引用指针的地址
    objc_object **referrer = (objc_object **)referrer_id;
    weak_entry_t *entry;
    if (!referent) return;

    // 通过对象地址referent在weak_table中找到对应的weak_entry_t
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        // 移除weak_entry_t中的弱引用referrer
        remove_referrer(entry, referrer);
        bool empty = true;
        
        //如果存储在referrers里面并且不为空
        if (entry->out_of_line()  &&  entry->num_refs != 0) {
            empty = false;
        }
        else {
            // 如果存储在inline_referrers里面,那么遍历inline_referrers如果有一个存在值empty就为false
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break;
                }
            }
        }

        // 如果当前这个weak_entry_t中没有值了
        if (empty) {
            weak_entry_remove(weak_table, entry);
        }
    }

    // 不要将referrer置为空,后面还可能指向其他对象
    // Do not set *referrer = nil. objc_storeWeak() requires that the 
    // value not change.
}

remove_referrer

/** 
 * Remove old_referrer from set of referrers, if it's present.
 * Does not remove duplicates, because duplicates should not exist. 
 * 将引用移除,不需要查重
 * 
 * @todo this is slow if old_referrer is not present. Is this ever the case? 
 *
 * @param entry The entry holding the referrers.
 * @param old_referrer The referrer to remove. 
 */
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
    // 如果是存储在inline_referrers中
    if (! entry->out_of_line()) {
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == old_referrer) {
                //找到对应的referrer置为nil
                entry->inline_referrers[i] = nil;
                return;
            }
        }
        // 出错了个球的了
        _objc_inform("Attempted to unregister unknown __weak variable "
                     "at %p. This is probably incorrect use of "
                     "objc_storeWeak() and objc_loadWeak(). "
                     "Break on objc_weak_error to debug.\n", 
                     old_referrer);
        objc_weak_error();
        return;
    }
    
    // 走到这里说明是存储到了referrers中了
    // 通过哈希运算得到对应的index
    size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    while (entry->referrers[index] != old_referrer) {
        index = (index+1) & entry->mask;
        if (index == begin) bad_weak_table(entry);
        hash_displacement++;
        if (hash_displacement > entry->max_hash_displacement) {
            _objc_inform("Attempted to unregister unknown __weak variable "
                         "at %p. This is probably incorrect use of "
                         "objc_storeWeak() and objc_loadWeak(). "
                         "Break on objc_weak_error to debug.\n", 
                         old_referrer);
            objc_weak_error();
            return;
        }
    }
    
    // 置为空
    entry->referrers[index] = nil;
    // 数量-1
    entry->num_refs--;
}

weak_entry_remove

/**
 * Remove entry from the zone's table of weak references.
 * 将entry移除
 */
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
    // remove entry
    // 如果是存储在referrers中,先把referrers的空间释放
    if (entry->out_of_line()) free(entry->referrers);
    // 释放entry
    bzero(entry, sizeof(*entry));
    // weak_table存储的entry数量-1
    weak_table->num_entries--;
    
    // 检查是不是之前扩容太多了,释放一部分空间
    weak_compact_maybe(weak_table);
}

weak_compact_maybe

// Shrink the table if it is mostly empty.
static void weak_compact_maybe(weak_table_t *weak_table)
{
    size_t old_size = TABLE_SIZE(weak_table);
    // Shrink if larger than 1024 buckets and at most 1/16 full.
    if (old_size >= 1024  && old_size / 16 >= weak_table->num_entries) {
        weak_resize(weak_table, old_size / 8);
        // leaves new table no more than 1/2 full
    }
}

一个小问题

        JPerson *strongPerson = [JPerson new];
        NSLog(@"strongPerson--%ld",CFGetRetainCount((__bridge CFTypeRef)(strongPerson)));
        
        __weak JPerson *weakPerson = person;
        NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(strongPerson)));
        NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(weakPerson)));
        NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(strongPerson)));

打印结果

strongPerson -- 1
strongPerson -- 1
weakPerson -- 2
strongPerson -- 1

我们知道weak指针是不会增加对象的引用计数的,从结果上看指针strongPerson指向的对象的引用计数也并没有增加,但是weakPerson的打印结果是2,这是怎么回事儿呢??通过汇编分析之

image.png

我们看到在weakPerson被用到之前调用了objc_loadWeakRetained,使用完之后立刻调用了objc_releaseobjc_release会使得对象引用计数-1,那么猜测objc_loadWeakRetained会让对象引用计数+1

objc_loadWeakRetained

/*
  这些注释没咋看明白,大概意思就是说weak没有给引用计数+1的话,
  如果在使用过程中对象释放就会出现内存问题
  Once upon a time we eagerly cleared *location if we saw the object 
  was deallocating. This confuses code like NSPointerFunctions which 
  tries to pre-flight the raw storage and assumes if the storage is 
  zero then the weak system is done interfering. That is false: the 
  weak system is still going to check and clear the storage later. 
  This can cause objc_weak_error complaints and crashes.
  So we now don't touch the storage until deallocation completes.
*/
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());
        // 这里给对象的引用计数+1
        if (! obj->rootTryRetain()) {
            result = nil;
        }
    }
    else {
        // Slow case. We must check for +initialize and call it outside
        // the lock if necessary in order to avoid deadlocks.
        if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
            BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
                class_getMethodImplementation(cls, @selector(retainWeakReference));
            if ((IMP)tryRetain == _objc_msgForward) {
                result = nil;
            }
            else if (! (*tryRetain)(obj, @selector(retainWeakReference))) {
                result = nil;
            }
        }
        else {
            table->unlock();
            class_initialize(cls, obj);
            goto retry;
        }
    }

    table->unlock();
    return result;
}

通过rootTryRetain方法给对象的引用计数+1

如果把一个weak指针赋值给另一个weak指针

    JPerson *person1 = [JPerson new];
    __weak JPerson * wPerson = person1;
    __weak JPerson * wPerson2 = wPerson;
    NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(wPerson)));
    NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(wPerson2)));

通过查看汇编看到调用了objc_copyWeak方法

/** 
 * This function copies a weak pointer from one location to another,
 * when the destination doesn't already contain a weak pointer. It
 * would be used for code like:
 * 拷贝weak指针
 *
 *  __weak id src = ...;
 *  __weak id dst = src;
 * 
 * This function IS NOT thread-safe with respect to concurrent 
 * modifications to the destination variable. (Concurrent weak clear is safe.)
 *
 * @param dst The destination variable. 新weak指针
 * @param src The source variable. 原来的weak指针
 */
void
objc_copyWeak(id *dst, id *src)
{
    id obj = objc_loadWeakRetained(src);
    objc_initWeak(dst, obj);
    objc_release(obj);
}

objc_copyWeak方法很简单,通过objc_loadWeakRetainedobjc_release保证操作期间对象不被释放,通过objc_initWeakweak指针和对象产生关联

weak指针自动释放原理

weak指针不会增加对象的引用计数,所以即使有weak指针指向对象那么对象也可以被正常释放,那么当对象被释放时又是如何释放weak指针的呢?

在对象释放之前会调用dealloc方法,我们追踪进去查看

dealloc

// Replaced by NSZombies
- (void)dealloc {
    _objc_rootDealloc(self);
}

void
_objc_rootDealloc(id obj)
{
    ASSERT(obj);
    obj->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);
    }
}

如果当前对象符合

  • isa.nonpointer,即isanonpointer标记位为1isa不是一个纯粹的指针,而是会包含弱引用指针、关联对象、引用计数等信息。
  • 没有若引用指针
  • 没有关联对象
  • 没有c++析构函数
  • 没有使用sidetable存储引用计数

那么直接free,否则调用object_dispose

id 
object_dispose(id obj)
{
    if (!obj) return nil;
    objc_destructInstance(obj);    
    free(obj);

    return nil;
}
 
 
/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory. 
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is 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;
}

inline void 
objc_object::clearDeallocating()
{
    // isa的nonpointer标记位为0,isa只是一个指针
    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.
        // 清理弱引用指针和sidetable中的引用计数
        clearDeallocating_slow();
    }
    assert(!sidetable_present());
}
// 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];
    table.lock();
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}

// 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];
    table.lock();
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}

追踪到这里终于找到了最终清理weak指针的地方

clearDeallocating_slow

/** 
 * Called by dealloc; nils out all weak pointers that point to the 
 * provided object so that they can no longer be used.
 * 
 * @param weak_table 
 * @param referent The object being deallocated.  referent为正在释放的对象
 */
void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;

    // 根据对象指针从weak_table中查找对应的entry
    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;
    
    // weak指针存储在referrers
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry); // mask+1的值
    } 
    else {
        // weak指针存储在inline_referrers
        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;
            }
            else if (*referrer) {
                _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();
            }
        }
    }
    // 将entry从weak_table中移除
    weak_entry_remove(weak_table, entry);
}

小结

entry中最终存储的是weak指针的地址和对象指针的对应关系,看一个小例子

        JPerson *person1 = [JPerson new]; //引用计数为1
        JPerson *person2 = person1; //引用计数为2
        JPerson *person3 = person1; //引用计数为3
        JPerson *person4 = person1; //引用计数为4
        __weak JPerson * wPerson = person1;
        
        // 引用计数临时+1=5
        NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(wPerson))); 
        
        // person1指向了别人
        person1 = [JPerson new]; //引用计数为1
        
        //这里是多少
        NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(wPerson)));

结果

5
4

image.png

弱引用表中存储的是weak指针的地址和对象地址的对应关系,我们分步骤理解

JPerson *person1 = [JPerson new];

栈区有一块空间的地址为person1,其内部存储的是一个地址,记为&obj,指向了一块堆空间,存储对象obj

__weak JPerson * wPerson = person1;

栈区开辟了一个空间地址wPersonwPerson处存储的值和person1相同也是&obj,同时在弱引用表的entry中存储了wPerson的地址和&obj的对应关系

// person1指向了别人
person1 = [JPerson new]; 

这时person1中不再存储&obj的地址,此时如果对象obj的引用计数不为0那么person1的离去对obj来说也只是少了一个引用计数而已,wPerson通过obj的地址仍然可以访问到obj