ARC 引用计数之weak

弱引用 - weak

本文使用的是objc4-706

打开NSObject.mm,首先看到的就是跟弱引用相关的代码。

我们先看一下几个宏定义

#ifdef __LP64__
#   define WORD_SHIFT 3UL
#   define WORD_MASK 7UL
#   define WORD_BITS 64
#else
#   define WORD_SHIFT 2UL
#   define WORD_MASK 3UL
#   define WORD_BITS 32
#endif

#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
#define SIDE_TABLE_DEALLOCATING      (1UL<<1)  // MSB-ward of weak bit
#define SIDE_TABLE_RC_ONE            (1UL<<2)  // MSB-ward of deallocating bit
#define SIDE_TABLE_RC_PINNED         (1UL<<(WORD_BITS-1)) 

#define SIDE_TABLE_RC_SHIFT 2
#define SIDE_TABLE_FLAG_MASK (SIDE_TABLE_RC_ONE-1)
复制代码

其中:

  • #define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0) 表示第一位是弱引用标志位,
  • #define SIDE_TABLE_DEALLOCATING (1UL<<1) 表示第二位是析构标志位
  • #define SIDE_TABLE_RC_ONE (1UL<<2) 表示第三位开始是存储引用计数数值
  • #define SIDE_TABLE_RC_PINNED (1UL<<(WORD_BITS-1)) 表示最后一位是引用计数数值的溢出表示位

接下来,就是跟弱引用中最重要的部分,存放着弱引用信息的SideTable这个hash table

struct SideTable {
  spinlock_t slock; // 自旋锁,防止资源竞争
  RefcountMap refcnts;  // 保存引用计数的散列表
  weak_table_t weak_table; // 全局的弱引用散列表
  
  ...
}
复制代码

从代码中,我们可以看到SideTable是一个结构体,有一个自旋锁和两个保存弱引用变量相关信息的散列表。

  • RefcountMap是一个简称,完整的类型是typedef objc:DenseMap<DisguisedPtr<objc_object>, size_t, true> RefcountMap这样一个散列表,其中key为DisguisedPtr<objc_object>, 表示指向对象的指针(location),value为size_t的数值,表示当前变量被引用的次数。

  • weak_table_t也是一个散列表,key是弱引用的对象(obj),value是weak_entry_t这个结构体。

    #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.
    */
    #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;
        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];
            };
        };
    
      ...
    };
    复制代码

    weak_entry_t存放了DisguisedPtr<objc_object> referent;被引用的对象和weak_referrer_t类型的weak指针。从上面可以看到,weak_entry_t有两种方式存储指针,当out_of_line_nes != REFERRERS_OUT_OF_LINE时,指针会存储在数组当中,否则的话,是存储在一个哈希集合当中。

好了,看完weak相关的数据结构,接下来我们看看整个流程是怎样的:

在main.m文件中添加下面的代码:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        __strong NSString *string = @"Hello";

        {
			__weak id weakObj = string;
                        NSLog(@"%@", weakObj);
                        weakObj = @"Hei";
                        NSLog(@"%@", weakObj);
        }
//        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
复制代码

将main.m文件编译为汇编代码:

根据得到的汇编代码,能够整理出一个weak变量的实现大概是:

{
  _objc_initWeak
  _objc_loadWeakRetained
  _objc_release
  _objc_storeWeak
  _objc_loadWeakRetained
  _objc_release
  _objc_destoryWeak
}
复制代码

可以看到,初始化和销毁分别是_objc_initWeakobjc_destoryWeak函数,而每一次调用weak变量的时候,都会出现一对_objc_loadWeakRetained_objc_release来对引用计数进行+1和-1。在对其进行赋新值的时候,调用的是_objc_storeWeak函数

API分析

  • id objc_initWeak(id *location, id newObj): 初始化一个weak指针,指向某个对象。location代表的是__weak指针的地址,newObj则是对象。

  • id objc_loadWeakRetained(id *location): 根据指针地址在SideTable中的RefcountMap中找到对应的值进行+1(objc_object::sidetable_tryRetain())

    RefcountMap::iterator it = table.refcnts.find(this);
      if (it == table.refcnts.end()) {
          table.refcnts[this] = SIDE_TABLE_RC_ONE;
      } else if (it->second & SIDE_TABLE_DEALLOCATING) {
          result = false;
      } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
          it->second += SIDE_TABLE_RC_ONE;
      }
    复制代码
  • void objc_release(id obj) { [obj release]; }这个方法就跟上面做的相反,是将引用计数-1(uintptr_t objc_object::sidetable_release(bool performDealloc)):

    RefcountMap::iterator it = table.refcnts.find(this);
      if (it == table.refcnts.end()) { // 没得减的话,就进行销毁
          do_dealloc = true;
          table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
      } else if (it->second < SIDE_TABLE_DEALLOCATING) {
          // 已经是销毁状态的话,不做任何更改
          do_dealloc = true;
          it->second |= SIDE_TABLE_DEALLOCATING;
      } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
          it->second -= SIDE_TABLE_RC_ONE;
      }
    复制代码
  • void objc_destroyWeak(id *location)销毁弱引用对象,最后跟initWeak一样,操作的还是storeWeak这个方法。

  • static id storeWeak(id *location, objc_object *newObj):(敲黑板,重点了)更新弱引用变量的值。上面说过了,weak变量的计数信息等都是存储在SideTable中的,storeWeak就是用于更新SideTable里面变量的信息的。

    复制代码

template <bool HaveOld, bool HaveNew, bool CrashIfDeallocating> 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]; } else { oldTable = nil; } if (HaveNew) { newTable = &SideTables()[newObj]; } 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.
if (HaveNew  &&  newObj) {
    Class cls = newObj->getIsa();
    if (cls != previouslyInitializedClass  &&  
        !((objc_class *)cls)->isInitialized()) 
    {
        SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
        _class_initialize(_class_getNonMetaClass(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);
}

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

SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);

return (id)newObj;
复制代码

}


根据源码,我们能了解到,weak变量的运作流程大概是:

复制代码
  • storeWeak
  • if HaveOld
  •  oldTable = &SideTables()[oldObj]; // 如果存在旧值的话,从全局散列表中数组中获取存储着旧值信息的散列表
    复制代码
  • if HaveNeew
  •  newTable = &SideTables()[newObj]; // 要设置新值的话,则取出新值的散列表
    复制代码
  • weak_unregister_no_lock(&oldTable->weak_table, oldObj, location) // 将location从旧的weak_table中移除
  • weak_register_no_lock(&newTable->weak_table, id(newObj), location, CrashIfDeallocationg) // 将新的obj和location添加到新的weak_table当中(sidetable_relaese)
  • newObj->setWeaklyReferenced_nolock(); // 将sidetable中的refcnts的对应的标志位设置为1,表示存在弱引用

  ***
  Note:

  看到一些之前的帖子,有介绍关于`TaggedPointer`的知识,不过当前版本中,Apple好像已经不再支持非64位环境的了,所有`isTaggedPointer()`都是返回了`false`

  > 有些对象如果支持使用 TaggedPointer,苹果会直接将其指针值作为引用计数返回;如果当前设备是 64 位环境并且使用 Objective-C 2.0,那么“一些”对象会使用其 isa 指针的一部分空间来存储它的引用计数;否则 Runtime 会使用一张散列表来管理引用计数
  ***
  
---

1. [Objective-C Automatic Reference Counting (ARC)](http://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-runtime-objc-initweak)
2. [Objective-C 引用计数原理](http://yulingtianxia.com/blog/2015/12/06/The-Principle-of-Refenrence-Counting/)复制代码

转载于:https://juejin.im/post/5a30e271f265da431a432f36

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值