iOS开发weak底层实现原理

weak关键字

weak是我们开发过程中很常见的关键字,使用场景如下:

  1. 声明弱引用属性
  2. 使用__weak来创建一个弱引用指针
    weak的主要作用就是用于内存管理,一个weak类型指针指向的对象被释放后,系统会自动将指针置为nil,防止其他代码访问造成野指针异常。接下来我们跟随源码来探索下系统是如何实现这种机制的。

代码

int main(int argc, const char * argv[]) {
  @autoreleasepool {

    DemoObject *object1 = [DemoObject new];
    DemoObject *object2 = [DemoObject new];

    __weak id wObject = object1;
    wObject = object2;
  }
   return 0;
}
weak调用流程.png

主要调用流程以及对应关系如下:
1.objc_initWeak---> __weak id wObject = object1
2.objc_storeWeak ---> wObject = object2
3.objc_destroyWeak ---> 出了作用域之后wObject指针需要被回收
4.objc_storeStrong ---> 出了作用域之后object1、object2指针需要被回收,调用了2次

源码追踪

接下来我们跟随libobjc的源码来探索weak的内部实现。 不过在此之前,我们需要来了解几个结构,以便于更好的阅读源码

相关的几个结构

StripedMap
 // RefcountMap disguises its pointers because we 
// don't want the table to act as a root for `leaks`.
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,RefcountMapValuePurgeable> RefcountMap;

// StripedMap<T> is a map of void* -> T, sized appropriately 
// 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.

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;
}
// 运算符重载取值
T& operator[] (const void *p) { 
    return array[indexForPointer(p)].value; 
}
...
 }

StripedMap是一个哈希表,包含了一个在ios下容量为8的数组,数组每一个元素是结构体PaddedT,内部包含了一个T类型的value变量。在本例中可以简单的理解为一个存储SideTable的哈希表,容量为8。

SideTable
// RefcountMap disguises its pointers because we 
// don't want the table to act as a root for `leaks`.
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,RefcountMapValuePurgeable> RefcountMap;

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

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(); }
void forceReset() { slock.forceReset(); }

...
};

SideTable有三个变量:

  • 互斥锁 slock
  • 引用计数表 refcnts
  • 弱引用表 weak_table

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; // entry数组
   size_t    num_entries; // 当前的entry个数
   uintptr_t mask; // 容量-1
   uintptr_t max_hash_displacement;
};

weak_entry_t

typedef DisguisedPtr<objc_object *> weak_referrer_t; // 可以理解为是一个对象指针,进行伪装的目的是为了避免被内存检测工具误认为出现内存泄漏

#if __LP64__
#define PTR_MINUS_2 62 // 共用体变量num_refs的位数
#else
#define PTR_MINUS_2 30
#endif

#define WEAK_INLINE_COUNT 4 // inline_referrers数组的容量
#define REFERRERS_OUT_OF_LINE 2 // 标识是用数组还是referrers存储弱引用指针

struct weak_entry_t {
DisguisedPtr<objc_object> referent; // 弱引用对象obj
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;
}
// entry的初始化方法,默认利用inline_referrers数组存储指针地址
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;
    }
}
};

这几个结构之间的关联关系如下:


关系图.png

大致了解了上面几个结构体之后,我们可以继续下面的步骤,在上面图中断点中直接点击Step into会直接进入objc_initWeak函数。

objc_initWeak

// Initialize a fresh weak pointer to some object location. 
id
objc_initWeak(id *location, id newObj)
 {
   if (!newObj) {
      *location = nil;
      return nil;
   }

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

对应的是__weak id wObject = object1

注意此时storeWeak的模板参数为:

  • DontHaveOld 没有旧值
  • DoHaveNew 有新值

storeWeak先略过,我们先看objc_storeWeak函数

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.

id
objc_storeWeak(id *location, id newObj)
{
   return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
    (location, (objc_object *)newObj);
}

对应的是wObject = object2;

注意此时storeWeak的模板参数为:

  • DoHaveOld 有旧值
  • DoHaveNew 有新值

从上述代码可以看出init/store方法一个是在初始化时调用,一个是重新赋值时调用,最终都调用了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.
// If HaveNew is true, there is a new value that needs to be 
//   assigned into the variable. This value might be nil.
// If CrashIfDeallocating is true, the process is halted if newObj is 
//   deallocating or newObj's class does not support weak references. 
//   If CrashIfDeallocating is false, nil is stored instead.
enum CrashIfDeallocating {
   DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
template <HaveOld haveOld, HaveNew haveNew,
      CrashIfDeallocating crashIfDeallocating>
static id 
storeWeak(id *location, objc_object *newObj)
{
ASSERT(haveOld  ||  haveNew);
if (!haveNew) ASSERT(newObj == nil);
// 声明需要的变量
Class previouslyInitializedClass = nil;
id oldObj; // weak指针指向的旧值
SideTable *oldTable; // 旧值对应的sidetable
SideTable *newTable; // 新值对应的sidetable

// 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; // 得到旧值和sidetable
    oldTable = &SideTables()[oldObj];
} else {
    oldTable = nil;
}
if (haveNew) {
    newTable = &SideTables()[newObj]; // 得到新值对应的sidetable
} else {
    newTable = nil;
}
// 加锁操作,避免出现多线程安全问题
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
// 有旧值但是和此时的弱引用指针指向不一致,说明出现了异常,需要重新尝试
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方法中出现相关弱引用的逻辑,此时initialize还没有执行完会出现递归调用,通过previouslyInitializedClass来打破递归
if (haveNew  &&  newObj) {
    Class cls = newObj->getIsa();
    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.
        // 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_table中的信息
    weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}

// Assign new value, if any.
if (haveNew) {
    // 在新值所对应的weak_table中的注册当前弱引用指针的信息
    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()) {
        // 更新object的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;
}

storeWeak代码比较多,大致分为一下三步:

1.从哈希表中获取新值和旧值对应的sidetable
2.weak_unregister_no_lock清理旧值在sidetable中对应的信息
3.weak_register_no_lock在新值对应的sidetable中注册对应的信息,同时更新新值的isa。

weak_unregister_no_lock 解除注册weak指针

void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                    id *referrer_id)
{
// 对应object
objc_object *referent = (objc_object *)referent_id;
// 指向weak指针的指针
objc_object **referrer = (objc_object **)referrer_id;

weak_entry_t *entry;

if (!referent) return;
// 在weak_table中寻找object对应的entry
if ((entry = weak_entry_for_referent(weak_table, referent))) {
    // 移除对应的entry里面的指针,只是解除了weak指针和当前object的关系,后面weak指针有可能会指向其他的对象。
    remove_referrer(entry, referrer);
    bool empty = true;
    // 判断entry是否为空,entry有两种存储方式,因此需要区分对待
    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;
            }
        }
    }
// entry为空时需要从weak_table中移除entry
    if (empty) {
        weak_entry_remove(weak_table, entry);
    }
}

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

梳理之后,上面的解除流程如下:
1.在weak_table中寻找referent_id(object对象)对应的entry
2.从entry中移除referrer(指向weak的指针)
3.entry为空时将entry从weak_table移除

weak_register_no_lock 注册weak指针

id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                  id *referrer_id, bool crashIfDeallocating)
{
//  object
objc_object *referent = (objc_object *)referent_id;
// weak指针的地址
objc_object **referrer = (objc_object **)referrer_id;

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

// now remember it and where it is being stored
weak_entry_t *entry; /**找到obj对应的weak_entry_t*/
if ((entry = weak_entry_for_referent(weak_table, referent))) {
    // 将地址插入到entry的数组中,其中会涉及到entry内部结构的变化
    append_referrer(entry, referrer);
} 
else {
    // 没有找到entry,需要新建一个entry
    weak_entry_t new_entry(referent, referrer); /** (对象, 指针)   
    // 加入之后weak_table可能发生容量的变化
    weak_grow_maybe(weak_table);
    // entry插入到weak_table中
    weak_entry_insert(weak_table, &new_entry);
}

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

   return referent_id;
}

和解除注册类似,需要通过object找到对应的entry之后,将弱引用指针地址存储对应的entry中,如果没有找到entry还需要创建新的entry并插入到weak_table中。

objc_destroyWeak

在上面的代码中,当作用域结束时,需要解除weak指针和指向对象的关联关系,这个地方调用的就是objc_destoryWeak方法。

void
objc_destroyWeak(id *location)
{
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
    (location, nil);
}

本质也是调用了storeWeak函数,只不过传入的参数为DoHaveOld, DontHaveNew,因此执行了解除注册的操作。

weak指针自动置nil的原理

因为weak并不会增加object的引用计数,因此在weak指针指向的情况下,object也会被释放,当weak指针指向的object被释放之后,系统会自动将weak指针指向nil,防止出现野指针异常,我们从dealloc方法着手,来看一下原理。

release

release流程.png

在dealloc打下断点,查看调用堆栈,可以发现当我们的object1出了作用域后,系统会调用objc_storeStrong(id *location, id obj)的方法


objc_storeString方法.png

其中的location记录了object1指针的地址,obj的值为nil,而prev为全局变量globalObject的地址。在这种情境下,objc_storeStrong主要就是触发了objc_release即object1的release操作。

仅在当前情况,后续的操作路程如下:
1.通过object1的isa_t,对其中的extra_rc也就是引用计数进行减操作
2.extra_rc溢出的时候会判断isa_t中的has_sidetable_rc的标识,发现没有使用sidetable来存储额外的引用计数
3.此时进入object1的dealloc流程,通过消息发送执行dealloc流程

dealloc

dealloc函数的反汇编实现如下图:


可以发现在 dealloc的最后调用了super的实现。

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

可以看到在rootDealloc方法中进行了一次判断:

  • 不是nonpointer
  • weakly_referenced:弱引用
  • has_assoc:关联对象
  • has_cxx_dtor: cxx析构函数
  • has_sidetable_rc: 使用sidetable存储了额外的引用计数

如果上述五个条件有任何一个命中,系统会执行object_dispose函数,否则会直接释放当前对象的内存空间。这五个条件中,其中有一个就是弱引用的标识weakly_referenced。

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); // 对自己的属性进行release操作
    if (assoc) _object_remove_assocations(obj); // 移除关联对象
    obj->clearDeallocating();
}

return obj;
}

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.
    // 处理有弱引用或者使用sidetable存储引用计数的non-pointer isa
    clearDeallocating_slow();
}

assert(!sidetable_present());
}

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_clear_no_lock

终于来到了weak指针相关的部分

void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
objc_object *referent = (objc_object *)referent_id;
// 找到对应的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;

if (entry->out_of_line()) {
    // 如果是以非inline的方式,count = mask+1
    referrers = entry->referrers;
    count = TABLE_SIZE(entry);
} 
else {
    // inline的方式count = 4
    referrers = entry->inline_referrers;
    count = WEAK_INLINE_COUNT;
}

for (size_t i = 0; i < count; ++i) {
    objc_object **referrer = referrers[i];
    if (referrer) {
        // 核心代码:弱引用指针置nil
        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();
        }
    }
}
// 移除weak_table中对应的entry
weak_entry_remove(weak_table, entry);
}

以上就是weak指针在指向的对象释放时自动置nil的原理。

关于weak_entry_t和weak_table的一些操作的具体实现

weak_entry_for_referent

在weak_table中查找object对应的entry

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;
// 通过哈希函数得到object在weak_entries中的位置
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
// 从得到的位置开始遍历哈希表,查找对应的entry
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

将指针地址放入对应的entry中

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
if (! entry->out_of_line()) {
    // Try to insert inline.
    // inline的情况下遍历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已经满了,需要开辟新的空间
    // 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的数组转移到重新开辟的new_referrers中, 这一次的操作是无效的,因为后面会对new_referrers进行扩容。。。
    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());
// 存储的entry超过了容量的3/4,需要进行扩容并插入弱引用指针的地址
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++;
}

grow_refs_and_insert

entry扩容并且插入弱引用指针的地址

__attribute__((noinline, used))
static void grow_refs_and_insert(weak_entry_t *entry, 
                             objc_object **new_referrer)
{
ASSERT(entry->out_of_line());

size_t old_size = TABLE_SIZE(entry);
// 容量翻倍
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;
// 旧的entry中的地址加入到新开辟的entry中
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
// 插入新的地址
append_referrer(entry, new_referrer);
// 释放旧的entry
if (old_refs) free(old_refs);
}

remove_referrer

static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
if (! entry->out_of_line()) {
    // inline的情况下只需要将对应的地址移除掉
    for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
        if (entry->inline_referrers[i] == old_referrer) {
            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;
}

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++;
    // 超过了最大的步数,表示出了异常,因为当前的max_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;
    }
}
// 将对应的地址指向nil,可能是因为超出了作用域此时的指针被栈回收了,因此需要置为nil,否则会出现野指针异常
entry->referrers[index] = nil;
entry->num_refs--;
}

weak_entry_remove

移除entry

 static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
// remove entry
if (entry->out_of_line()) free(entry->referrers);
bzero(entry, sizeof(*entry));

weak_table->num_entries--;

weak_compact_maybe(weak_table);
}

weak_compact_maybe

weak_table的size太大,但是内部的entry个数太小时,需要缩减容量

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

weak_grow_maybe

检测weak_table是否需要扩容

static void weak_grow_maybe(weak_table_t *weak_table)
{
size_t old_size = TABLE_SIZE(weak_table);/**根据 mask来获取原有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

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;
    // 得到原有entry的终点
    weak_entry_t *end = old_entries + old_size;
    // 遍历整个entry,插入到新开辟的空间中
    for (entry = old_entries; entry < end; entry++) {
        if (entry->referent) {
            weak_entry_insert(weak_table, entry);
        }
    }
    free(old_entries);
  }
}

小结

当第一次创建某个对象的弱引用时,会以该对象的指针和弱引用的地址创建一个 weak_entry_t,并放在该对象所处的 SideTable 的 weak_table_t 中,然后以后所有指向该对象的弱引用的地址都会保存在该对象的 weak_entry_t 的哈希数组中,当该对象要析构时,遍历 weak_entry_t 内部数组中保存的弱引用的地址,将弱引用指向 nil,最后将 weak_entry_t 从 weak_table 中移除。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容