Runtime学习 -- weak应用源码学习

  Runtime源码分析,带你了解OC实现过程。其中参考了大量的大神的代码以及文献,里面也有个人的见解,欢迎拍砖,欢迎交流。

两种常见使用场景

/// weak属性
@interface XX : XX
@property(nonatomic,weak) Type* weakPtr;
@end /// 代码块中使用
{
/// 使用__weak
__weak Type* weakPtr = [[SomeObject alloc] init];
}

根据调试信息,发现两者的区别是:

  • 第一种进入到 id objc_storeWeak(id *location, id newObj)方法
/**
* 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
* @param newObj The new object this weak ptr should now point to
*
* @return \e newObj
*/
id
objc_storeWeak(id *location, id newObj)
{
return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object *)newObj);
}
  • 第二种绕一个远路,先初始化 id objc_initWeak(id *location, id newObj)
/**
* 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.
* @param newObj Object ptr.
*/
id objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
} return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
  • 两者最终进入到如下方法
template <HaveOld haveOld, HaveNew haveNew,
CrashIfDeallocating crashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj)
{
///略去,下面会进行分析
...
return (id)newObj;
}

所以重点就在 storeWeak这个方法中,let's do it

分析源码

storeWeak源码的如下:

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;
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.
/// 注释大意是通过下面操作,保证所有的弱引用对象的isa都被初始化,这样可以防止死锁,PS,这里我不是太明白,求指教
if (haveNew && newObj) {
/// 下面的操作是初始化isa
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;
}
  • template <HaveOld haveOld, HaveNew haveNew,CrashIfDeallocating crashIfDeallocating>是C++的一种泛型实现,相当于这里申明了变量或者类型,可以在代码块中使用,用于处理不同的未知类型&枚举。
  • haveOld 弱引用是否已经有所指向
  • haveNew 是否有新的指向
  • CrashIfDeallocating 执行方法时发生Deallocate是否Crash

PS:初始化ISA那部分为何能阻止死锁,我没有看懂

该函数流程如下:

重点来了:

/// SideTables
oldTable = &SideTables()[oldObj];
newTable = &SideTables()[newObj];
/// taggedPointer是什么鬼
isTaggedPointer
/// 注册弱引用
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,crashIfDeallocating);
/// 消除弱引用
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);

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.");
} ///锁
....
};
  • spinlock_t solck 锁
  • RefcountMap refcnts 强引用使用,略过
  • weak_table_t weak_table 弱引用表

    SideTable是存放引用关系的,对象通过Hash值操作,在SideTableBuf 中寻找与之对应的SideTableSideTableBuf初始化过程如下:
alignas(StripedMap<SideTable>) static uint8_t
SideTableBuf[sizeof(StripedMap<SideTable>)];
/// 会在Objc_init中调用该方法
static void SideTableInit() {
/// 这句话貌似没什么卵用,求指教
new (SideTableBuf) StripedMap<SideTable>();
}
/// 寻找SideTable
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}

StripedMap是一个泛型类,并重写了[]运算符,通过对象的地址,运算出Hash值,通过该hash值找到对象的SideTable

template<typename T>
class StripedMap {
enum { CacheLineSize = 64 };
#if TARGET_OS_EMBEDDED
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);
/// 位运算可以控制返回值在0-63之间
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
} public:
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}
/// 下面略去
...
}

taggedPointer

简单的说,这是一种优化手段,即将对象的值,存入对象的地址中,这些工程师简直丧心病狂,就为了省一点内存嘛!

进入正题,看看怎么实现弱引用的

先看看注册的过程吧

/**
* Registers a new (object, weak pointer) pair. Creates a new weak
* object entry if it does not exist.
*
* @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)
{
/// 转化为object
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
/// 如果是taggedPointer,就没有引用的过程了
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,
SEL_allowsWeakReference);
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, SEL_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;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else {
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
} // Do not set *referrer. objc_storeWeak() requires that the
// value not change. return referent_id;
}

先从这行数的参数说起,参数有4个

  • weak_table_t *weak_table hash表
  • id referent_id, 弱引用对象
  • id *referrer_id, 弱引用指针
  • bool crashIfDeallocating 如果正在Deallocate是否crash

后三个参数不用解释,主要解释第一个参数,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; ///数组,用于存储引用对象集合
size_t num_entries; /// 存储数目
uintptr_t mask; /// 当前分配容量
uintptr_t max_hash_displacement; /// 已使用容量
};

没错,weak_table_t就是寄存在SideTable

  • weak_entry_t *weak_entries; ///数组,用于存储引用对象集合
  • 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是最终存放对象和引用指针的地方,referent是被引用的对象,联合体union释义如下

  • weak_referrer_t *referrers; 存放引用指针
  • uintptr_t out_of_line_ness : 2 标识当前存储是否在初始WEAK_INLINE_COUNT个数之内
  • uintptr_t num_refs : PTR_MINUS_2 引用的个数
  • uintptr_t mask; 实际分配容量
  • uintptr_t max_hash_displacement; 实际使用容量,包括已经被释放的,每次调整容量时会更新重置
  • weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; 当引用个数小于WEAK_INLINE_COUNT时,使用该数组存放。

注册引用过程中,重点关注下面代码:

{
weak_entry_t *entry;
/// 查找是否已经注册过了
if ((entry = weak_entry_for_referent(weak_table, referent))) {
/// 加上去就可以了
append_referrer(entry, referrer);
}
else {
/// 新建一个
weak_entry_t new_entry(referent, referrer);
/// 调整weak_table_t 的容量大小
weak_grow_maybe(weak_table);
/// 插入一个
weak_entry_insert(weak_table, &new_entry);
}
}

新建

通过weak_entry_t的源码,可以看到新建一个weak_entry_t的过程是

  • 将被引用对象赋予referent
  • 将引用指针放入到inline_referrers,因为此时数目还很少

调整weak_table_t的容量大小

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

当实际的数目大于old_size(old_size就是mask的大小+1),就去调整大小,同时重置max_hash_displacement为0,通过calloc函数,动态分配mask个的内存,然后通过循环,将原有的weak_entry_t插入到新的容器中,在插入的过程中,更新max_hash_displacement.

weak_table_t插入weak_entry_t

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); 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++;
}
/// 把新的加进去
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;
}
}

过程比较简单,也是利用hash处理,方便后面查找。

weak_table_t查找对象是通过循环遍历的方式,过程如下

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; size_t begin = hash_pointer(referent) & weak_table->mask; /// 获取hash值
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;
}
} return &weak_table->weak_entries[index];
}

在已有的weak_entry_t中加入引用

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
/// 如果是数组,即个数比较少
if (! entry->out_of_line()) {
// Try to insert inline.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return;
}
} // 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.
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()); 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++;
}

该过程同在weak_table_t中插入weak_entry_t如出一辙,要注意的是需要判断引用的个数,当引用个数大于WEAK_INLINE_COUNT时,需要将原有的引用指针也移到referrers中,同时更新相关计数器。

上面过程的流程如下:

消除弱引用

消除弱引用过程同注册大致相同,只是部分地方是相反操作,不做赘述了

OC对象之旅 weak弱引用实现分析的更多相关文章

  1. weak 弱引用的实现方式

    来源:冬瓜争做全栈瓜 链接:https://desgard.com/weak/ 对于 runtime 的分析还有很长的路,最近在写 block 系列的同时,也回顾一下之前疏漏的细节知识.这篇文章是关于 ...

  2. [翻译]Understanding Weak References(理解弱引用)

    原文 Understanding Weak References Posted by enicholas on May 4, 2006 at 5:06 PM PDT 译文 我面试的这几个人怎么这么渣啊 ...

  3. weakref:对象的弱引用

    介绍 weakref支持对象的弱引用,正常的引用会增加对象的引用计数,并避免它被垃圾回收.但结果并不是总和期望的那样,比如有时候可能会出现一个循环引用,或者有时候需要内存时可能要删除对象的缓存.而弱引 ...

  4. C#中考虑为大对象使用弱引用

    1.无论怎样尽力,我们总是会使用到某些需要大量内存的数据,而这些内存并不需要经常访问.或许你需要从一个大文件中查找某个特定的值,或者算法需要一个较大的查询表.这时,你也许会采用2中不太好做法:第一种是 ...

  5. 理解Java中的弱引用(Weak Reference)

    本篇文章尝试从What.Why.How这三个角度来探索Java中的弱引用,理解Java中弱引用的定义.基本使用场景和使用方法.由于个人水平有限,叙述中难免存在不准确或是不清晰的地方,希望大家可以指出, ...

  6. 小结OC中Retain cycle(循环引用)

    retain cycle 的产生 说到retain cycle,首先要提一下Objective-C的内存管理机制. 作为C语言的超集,Objective-C延续了C语言中手动管理内存的方式,但是区别于 ...

  7. IOS 看懂此文,你的block再也不需要WeakSelf弱引用了!

    前言: 最近都在折腾 Sagit 架框的内存释放的问题,所以对这一块有些心得. 对于新手,学到的文章都在教你用:typeof(self) __weak weakSelf = self. 对于老手,可能 ...

  8. 【转载】objective-c强引用与弱引用

    形象比喻蛮好玩的^_^    __weak 和 __strong 会出现在声明中   默认情况下,一个指针都会使用 __strong 属性,表明这是一个强引用.这意味着,只要引用存在,对象就不能被销毁 ...

  9. 谈谈.NET中常见的内存泄露问题——GC、委托事件和弱引用

    其实吧,内存泄露一直是个令人头疼的问题,在带有GC的语言中这个情况得到了很大的好转,但是仍然可能会有问题.一.什么是内存泄露(memory leak)?内存泄露不是指内存坏了,也不是指内存没插稳漏出来 ...

随机推荐

  1. C# 事务之SqlTransaction

    private static void Execute(string connectionString) { using (SqlConnection connection = new SqlConn ...

  2. Swift try try! try?使用和区别

    Swift try try! try?使用和区别 一.异常处理try catch的使用 1. swift异常处理 历史由来 Swift1.0版本 Cocoa Touch 的 NSError ,Swif ...

  3. Unix系统操作指令汇总

    一.目录及文件操作命令 1.1 ls 语法: ls [-RadCxmlnogrtucpFbqisf1] [目录或文件--] 说明: ls 命令列出指定目录下的文件,缺省目录为当前目录 ./,缺省输出顺 ...

  4. PHP 学习笔记(4)

    声明类属性或方法为静态,就可以不实例化类而直接访问.静态属性不能通过一个类已实例化的对象来访问(但静态方法可以). PHP 5 支持抽象类和抽象方法.定义为抽象的类不能被实例化 使用接口(interf ...

  5. GBDT与LR融合提升广告点击率预估模型

    1GBDT和LR融合      LR模型是线性的,处理能力有限,所以要想处理大规模问题,需要大量人力进行特征工程,组合相似的特征,例如user和Ad维度的特征进行组合.      GDBT天然适合做特 ...

  6. RocketMQ 介绍与基本使用

    介绍 RocketMQ是阿里巴巴自研的第三代分布式消息中间件,是阿里系下开源的一款分布式.队列模型的消息中间件,原名Metaq,3.0 版本名称改为RocketMQ,是阿里参照kafka设计思想使用J ...

  7. ST-4

    1.(49-7)使用下面的方法printPrimes()完成后面的问题: (a)为printPrimes()方法画控制流图. (b)考虑测试用例t1=(n=3)和t2=(n=5).即使这些测试用例游历 ...

  8. jQuery选择器的优点

    jQuery选择器的优点 相信小伙伴们对选择器并不陌生,从css1到css3的选择器有很多,但是JQuery都能完美的支持,而且API操作起来也特别方便好用,在很大程度上精简了代码,节约了很多性能.那 ...

  9. 不依赖浏览器控制台的JavaScript断点调试方法

    随着浏览器的逐渐强大,绝大多数情况下的代码调试都是可以通过浏览器自带的一些调试工具进行解决.然而对于一些特殊情况仍然无法享受到浏览器的强大 调试能力,比如QQ客户端内嵌web的调试(虽然说QQ目前已经 ...

  10. hosts文件原理

    hosts文件是一个用于储存计算机网络中各节点信息的计算机文件.这个文件负责将主机名映射到相应的IP地址.hosts文件通常用于补充或取代网络中DNS的功能.和DNS不同的是,计算机的用户可以直接对h ...