有关于weak的底层实现

265 阅读4分钟

有关于weak的底层实现

C++标准转换运算符reinterpret_cast

在探索weak底层源码的时候,看到这个运算符并记录一下:

reinterpret_cast<new_type> (expression)

reinterpret_cast运算符是用来处理类型之间的转换,返回新类型的值,这个值会与原始参数(expression)有相同的bit位。 IBM的C++指南里告诉我们应该在什么地方转换运算符:

  • 从指针类型到一个足够大的整数类型

  • 从整数类型或者枚举类型到指针类型

  • 从一个指向函数的指针到另一个不同类型的指向函数的指针

  • 从一个指向对象的指针到另一个不同类型的指向对象的指针

  • 从一个指向类函数成员的指针到另一个指向不同类型的函数成员的指针

  • 从一个指向类数据成员的指针到另一个指向不同类型的数据成员的指针

      typedef void (*Func)(int,int);
      Func f;
      int value = 2;
      f = reinterpret_cast<Func>(&value);
      f(1,2);
    

简单的在Xcode做个测试,成功的编译通过,不过一旦运行就会崩溃,产生“EXC_BAD_ACCESS”的错误,因为通过函数f所指的地址找到的并不是函数的入口。IBM的C++指南、C++之父Bjarne Stroustrup的FAQ网页和MSDN的Visual C++也都指出:错误的使用reinterpret_cast很容易导致程序的不安全,只有将转换后的类型值转换回到其原始类型,这样才是正确使用reinterpret_cast方式。

参考文献:

www.cnblogs.com/lsgxeva/p/1…

探索weak底层的实现

weak用法

在我们的日常开发中,常常看到的是以下两种用法:

1、引用自源码的注释

 *(The nil case) 
 * __weak id weakPtr;
 * (The non-nil case) 
 * NSObject *o = ...;
 * __weak id weakPtr = o;

2、

@property(nonatomic, weak) Test *t;

在使用断点源码调试的过程中,以上两种方式的入口以及方法执行的逻辑顺序稍有不同,我们先来看第一种方式:

id objc_initWeak(id *location, id newObj)
{
  if (!newObj) {
       *location = nil;
       return nil;
   }
    return storeWeak<DontHaveOld = false, DoHaveNew = true, DoCrashIfDeallocating = true>
    (location, (objc_object*)newObj);
}

weak内部实现流程

首先调用的是objc_initWeak,判断传入的对象是否为空,如果为空,则返回nil,反之调用storeWeak函数,storeWeak()函数的作用是更新指针指向,创建对应的弱引用表,storeWeak()函数内部大致实现流程:

  • 1.创建新旧散列表,oldTable,newTable;
  • 2.获取以oldObj、newObj为索引存储的地址;
  • 3.加锁操作,防止多线程竞争冲突;
  • 4.判断传入的newObj对应的类是否初始化;
  • 5.清除旧值weak_unregister_no_lock
  • 6.分配新值weak_register_no_lock

而通过属性声明的对象,会根据修饰词的不同调用不同的函数

switch (memoryManagement) {
case objc_ivar_memoryWeak:       objc_storeWeak(location, value); break;
case objc_ivar_memoryStrong:     objc_storeStrong(location, value); break;
case objc_ivar_memoryUnretained: *location = value; break;
case objc_ivar_memoryUnknown:    _objc_fatal("impossible");
}

通过weak修饰的对象在调用set方法时会调用objc_storeWeak函数。

SideTable

struct SideTable {
// 保证原子操作的自旋锁
spinlock_t slock;
// 引用计数的 hash 表
RefcountMap refcnts;
// weak 引用全局 hash 表
weak_table_t weak_table;
}

从源码可以看到,SideTable是个结构体,结构体内的第一个成员变量是为了防止竞争选择的自旋锁,第二个是协助对象isa指针的extra_rc共同引用计数的变量(前面我们在探索isa_t bits位域内有提到extra_rc,保存不超过10的引用计数变量),第三个则是全局的弱引用hash表,我们再来看看weak_table_t的结构:

 struct weak_table_t {
 // 保存了所有指向指定对象的 weak 指针
 weak_entry_t *weak_entries;
 // 存储空间
size_t    num_entries;
// 参与判断引用计数辅助量
uintptr_t mask;
// hash key 最大偏移值
uintptr_t max_hash_displacement;
};

使用不定类型对象的地址作为 key ,用 weak_entry_t 类型结构体对象作为 value 。其中weak_entry_t是负责维护和存储指向一个对象的所有弱引用的hash表,其定义如下:

struct weak_entry_t {
DisguisedPtrobjc_object> referent;
union {
    struct {
        weak_referrer_t *referrers;
        uintptr_t        out_of_line : 1;
        uintptr_t        num_refs : PTR_MINUS_1;
        uintptr_t        mask;
        uintptr_t        max_hash_displacement;
    };
    struct {
        // out_of_line=0 is LSB of one of these (don't care which)
        weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
    };
}

}

总结如下图所示:

当weak引用指向的对象被释放时,又是如何去处理weak指针的呢?当释放对象时,其基本流程如下:

1、调用objc_release
2、因为对象的引用计数为0,所以执行dealloc
3、在dealloc中,调用了_objc_rootDealloc函数
4、在_objc_rootDealloc中,调用了object_dispose函数
5、调用objc_destructInstance
6、最后调用objc_clear_deallocating