weak功能就不多说了,它的实现原理就从一段代码开始吧。
一个OC
变量的默认属性都是strong
,所以我们如果需要weak
属性的变量就需要显示的标记出来。
int main(int argc, char * argv[]) {
@autoreleasepool {
NSObject *obj1 = [[NSObject alloc]init];
__weak NSObject *obj2 = obj1;
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
我们把上面这段代码转换成C++
就会变成这个样子
...//一堆的函数、属性、结构体等等的定义,不是我们关注的重点
...
//main函数的c++实现
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSObject *obj1 = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
__attribute__((objc_ownership(weak))) NSObject *obj2 = obj1;
return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
}
}
然后在 return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
上打上断点,进入汇编语言,我们可以看到main
函数的汇编实现,摘取一些看得懂的,可以看出一些函数的调用栈
0x1047397d9 <+25>: callq 0x10473b37a ; symbol stub for: objc_autoreleasePoolPush
0x1047397de <+30>: movq 0x534b(%rip), %rdi ; (void *)0x0000000104fa3170: NSObject
0x1047397e9 <+41>: callq 0x10473b36e ; symbol stub for: objc_alloc
0x1047397ee <+46>: movq 0x51d3(%rip), %rsi ; "init"
0x1047397f5 <+53>: movq 0x382c(%rip), %rdi ; (void *)0x0000000104c5a800: objc_msgSend
0x104739815 <+85>: callq 0x10473b392 ; symbol stub for: objc_initWeak
0x104739821 <+97>: movq 0x5310(%rip), %rcx ; (void *)0x000000010473ec28: AppDelegate
0x104739828 <+104>: movq 0x5209(%rip), %rdx ; "class"
0x104739853 <+147>: callq 0x10473b350 ; symbol stub for: NSStringFromClass
0x104739858 <+152>: movq %rax, -0x68(%rbp)
0x104739865 <+165>: callq 0x10473b3b0 ; symbol stub for: objc_retainAutoreleasedReturnValue
0x104739881 <+193>: callq 0x10473b356 ; symbol stub for: UIApplicationMain
0x10473989b <+219>: callq *0x378f(%rip) ; (void *)0x0000000104c57d70: objc_release
0x1047398a8 <+232>: callq 0x10473b386 ; symbol stub for: objc_destroyWeak
0x1047398b8 <+248>: callq 0x10473b3b6 ; symbol stub for: objc_storeStrong
0x1047398c1 <+257>: callq 0x10473b374 ; symbol stub for: objc_autoreleasePoolPop
汇编语言实在看不懂,不过通过上面三段代码的对比我们大概知道,初始化obj1
之后,赋值给weak
属性的obj2
时,调用了objc_initWeak
函数。
可以在objc4源码中找到objc_initWeak
实现,可以看到就是一个简单的判断之后调用了storeWeak
函数,storeWeak
函数中才真正实现了weak
引用。
storeWeak
函数工作流程
- 在进行真正的引用工作之前,先要做好一些列的准备工作:
- 若
weak
指针有指向的对象, 先获取weak
指针原有对象的SideTable
引用计数表; - 若被引用
newObj
有值, 则获取newObj
的引用计数表; - 对上面两个引用计数表加锁;
- 判断值是否被修改,如若被修改,解锁,重新开始;
- 判断被引用对象是否完成
isa
指针初始化,如果没完成,解锁,重新开始
- 若
- 在准备工作完成之后,会调用
weak_unregister_no_lock()
方法来从原有的表中先删除这个weak
指针。 - 然后再调用
weak_register_no_lock()
来向对应的表中插入这个weak
指针;把被应用对象设置为弱引用表;把被引用对象的地址赋值给weak
指针指向的地址。 - 解锁两个引用表,完成
weak
引用。
//定义了一个函数模版
template
static id
storeWeak(id *location, objc_object *newObj)
{
//简单的判断,看是否有值
assert(haveOld || haveNew);
if (!haveNew) assert(newObj == nil);
Class previouslyInitializedClass = nil;
//用于暂存location指向的地址
id oldObj;
//location原值指向的引用计数表
SideTable *oldTable;
//newObj指向的引用计数表
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重新做一遍流程
retry:
if (haveOld) {
oldObj = *location;
//获取location原指向地址的引用计数表
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
//获取newObj的引用计数表
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
//对两个引用计数表加锁
SideTable::lockTwo(oldTable, newTable);
//如果location有值而且location != oldObj,那就是location的值被改变了(因为oldObj = *location;这一步已经赋值给oldObj,理论上应该是相等的),需要解锁,从头开始
if (haveOld && *location != oldObj) {
SideTable::unlockTwo(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指针已经完成初始化工作
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo(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.
//把location原有的弱引用清除掉
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
if (haveNew) {
//将location添加到newObj的弱引用表中
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
//如果添加弱引用被拒绝,weak_register_no_lock会返回nil
// Set is-weakly-referenced bit in refcount table.
// 将newObj设置为被弱引用状态
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
// 只能在这里把location指向新值(newObj)
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
//对两个引用表解锁
SideTable::unlockTwo(oldTable, newTable);
return (id)newObj;
}
上面这段代码就是runtime
实现weak
引用的全部过程,其中有几个数据结构非常重要,分别是SideTables
,SideTable
,weak_table_t
和weak_entry_t
。它们和对象的引用计数,以及weak引用相关。
先说一下这四个数据结构的关系。 SideTables
是一个64个元素长度的hash数组,里面存储了SideTable
。SideTables
的hash键值就是一个对象obj的address。
因此可以说,一个obj,对应了一个SideTable
。但是一个SideTable
,会对应多个obj。因为SideTable
的数量只有64个,所以会有很多obj共用同一个SideTable
。
而在一个SideTable
中,有三个成员,分别是
spinlock_t slock; //自旋锁,用于对SideTable操作时将其锁定
RefcountMap refcnts; //对象引用计数相关信息
weak_table_t weak_table; //对象的弱引用相关信息
其中,refcents
是一个hash map,其key是obj的地址,而value,则是obj对象的引用计数。而weak_table
则存储了弱引用obj的指针的地址,其本质是一个以obj地址为key,弱引用obj的指针的地址作为value的hash表。hash表的节点类型是weak_entry_t。
SideTables
先来说一下最外层的SideTables
。SideTables
可以理解为一个全局的hash数组,里面存储了SideTable
类型的数据,其长度为64。
但是SideTabls
并不是一个被定义的数据类型,它只是一个全局静态函数,返回值是一个StripedMap
类型,所以其实SideTables
类型就是StripedMap
类型
static StripedMap& SideTables() {
return *reinterpret_cast*>(SideTableBuf);
}
看一下StripedMap
类的数据结构
//参数模版化,这里讨论的T为SideTable
template
class StripedMap {
// TARGET_OS_IPHONE 目前写死为0,所以StripeCount只能是64
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
struct PaddedT {
// T(模版参数,这里就是SideTable) 64字节对齐
T value alignas(CacheLineSize);
};
//所有PaddedT struct 类型数据被存储在一个长度为64的array数组中
PaddedT array[StripeCount];
// 该方法以void *作为key 来获取void *对应在StripedMap中的下标位置
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;// % StripeCount 是为了防止下标越界
}
public:
//获取void *对应的SideTable
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}
const T& operator[] (const void *p) const {
return const_cast>(this)[p];
}
....//一些其他操作
};
这里的逻辑代码写的很清晰,我后面省略了一系列的锁操作,可以自己在runtime
的代码里看一下。
从中可以看到,所有的StripedMap
锁操作,最终是调用的array[i].value
的相关操作。因此,对于模版参数T类型,必须具备相关的lock
操作接口。
因此,要作为StripedMap
哈希表的模版参数,对于T类型还是有所要求的(就是能够进行锁操作)。而在SideTables
中,T即为SideTable
类型。
SideTable
SideTable
的定义很清晰,有三个成员:
-
spinlock_t slock
: 自旋锁,用于上锁/解锁SideTable
。 -
RefcountMap refcnts
:以DisguisedPtr
为key的哈希表,用来存储OC对象的引用计数(仅在未开启isa优化 或 在isa优化情况下isa_t的引用计数溢出时才会用到)。 -
weak_table_t weak_table
: 存储对象弱引用指针的哈希表。是OC weak
功能实现的核心数据结构。
除了三个成员外,苹果为SideTable
还写了构造和析构函数,在析构函数的源码中可以看出来,SideTable
是不能被析构的。
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
最后是一堆锁操作,用于多线程访问SideTable
, 同时,也符合我们上面提到的StripedMap
中关于T模版参数的lock
接口定义。
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(); }
// Address-ordered lock discipline for a pair of side tables.
// 按锁地址顺序对两个SideTable加锁,以防止锁排序问题
template
static void lockTwo(SideTable *lock1, SideTable *lock2);
template
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
spinlock_t slock
spinlock_t
的最终定义实际上是一个uint32_t
类型的非公平的自旋锁。所谓非公平,就是说获得锁的顺序和申请锁的顺序无关,也就是说,第一个申请锁的线程有可能会是最后一个获得到该锁,或者是刚获得锁的线程会再次立刻获得到该锁,造成饥饿等待。 同时,在OC中,_os_unfair_lock_opaque
也记录了获取它的线程信息,只有获得该锁的线程才能够解开这把锁。
typedef struct os_unfair_lock_s {
uint32_t _os_unfair_lock_opaque;
} os_unfair_lock, *os_unfair_lock_t;
关于自旋锁的实现,苹果并未公布,但是大体上应该是通过操作_os_unfair_lock_opaque
这个uint32_t
的值,当大于0时,锁可用,当等于或小于0时,需要锁等待。
RefcountMap refcnts
可以看这个
RefcountMap refcnts
用来存储OC对象的引用计数。它实质上是一个以objc_object
为key的哈希表,其vaule
就是OC对象的引用计数。同时,当OC对象的引用计数变为0时,会自动将相关的信息从哈希表中剔除。
RefcountMap
的本质是一个DenseMap
类,本文中的调用模板的三个类型参数DisguisedPtr
,size_t
, true
分别表示DenseMap
的hash key
类型,value
类型,是否允许引用计数为0的节点被使用。
template >
class DenseMap
: public DenseMapBase,
KeyT, ValueT, KeyInfoT, ZeroValuesArePurgeable>
-
ZeroValuesArePurgeable
默认值是false
, 但RefcountMap
指定其初始化为true
。 这个成员标记是否可以使用值为 0 (引用计数为 1) 的桶。 因为空桶存的初始值就是 0,所以值为 0 的桶和空桶没什么区别。如果允许使用值为 0 的桶, 查找桶时如果没有找到对象对应的桶,也没有找到墓碑桶,就会优先使用值为 0 的桶。 -
Buckets
指针管理一段连续内存空间,也就是数组,数组成员是BucketT
类型的对象,我们这里将BucketT
对象称为桶(实际上这个数组才应该叫桶,苹果把数组中的元素称为桶应该是为了形象一些,而不是哈希桶中的桶的意思)。桶数组在申请空间后,会进行初始化,在所有位置上都放上空桶(桶的 key 为EmptyKey
时是空桶),之后对引用计数的操作,都要依赖于桶。
桶的数据类型实际上是std::pair
,类似于swift
中的元祖类型,就是将对象地址和对象的引用计数(这里的引用计数类似于 isa,也是使用其中的几个 bit 来保存引用计数,留出几个 bit 来做其它标记位)组合成一个数据类型。 -
NumEntries
记录数组中已使用的非空的桶的个数。 -
NumTombstones
Tombstone
直译为墓碑, 当一个对象的引用计数为0,要从桶中取出时,其所处的位置会被标记为Tombstone
。NumTombstones
就是数组中的墓碑的个数。后面会介绍到墓碑的作用。 -
NumBuckets
桶的数量,因为数组中始终都充满桶,所以可以理解为数组大小。
RefcountMap 的工作逻辑
- 通过计算对象地址的哈希值, 来从
SideTables
中获取对应的SideTable
. 哈希值重复的对象的引用计数存储在同一个SideTable
里. -
SideTable
使用find()
方法和重载 [] 运算符的方式, 通过对象地址来确定对象对应的桶. 最终执行到的查找算法是LookupBucketFor()
. - 查找算法会先对桶的个数进行判断, 如果桶数为 0 则
return false
回上一级调用插入方法. 如果查找算法找到空桶或者墓碑桶, 同样return false
回上一级调用插入算法, 不过会先记录下找到的桶. 如果找到了对象对应的桶, 只需要对其引用计数+ 1
或者- 1
. 如果引用计数为 0 需要销毁对象, 就将这个桶中的 key 设置为TombstoneKey
- 插入算法会先查看可用量, 如果哈希表的可用量(墓碑桶+空桶的数量)小于 1/4, 则需要为表重新开辟更大的空间, 如果表中的空桶位置少于 1/8 (说明墓碑桶过多), 则需要清理表中的墓碑. 以上两种情况下哈希查找算法会很难查找正确位置, 甚至可能会产生死循环, 所以要先处理表, 处理表之后还会重新分配所有桶的位置, 之后重新查找当前对象的可用位置并插入. 如果没有发生以上两种情况, 就直接把新的对象的引用计数放入调用者提供的桶里.
bool LookupBucketFor(const LookupKeyT &Val,
const BucketT *&FoundBucket) const {
...
if (NumBuckets == 0) { //桶数是0
FoundBucket = 0;
return false; //返回 false 回上层调用添加函数
}
...
unsigned BucketNo = getHashValue(Val) & (NumBuckets-1); //将哈希值与数组最大下标按位与
unsigned ProbeAmt = 1; //哈希值重复的对象需要靠它来重新寻找位置
while (1) {
const BucketT *ThisBucket = BucketsPtr + BucketNo; //头指针 + 下标, 类似于数组取值
//找到的桶中的 key 和对象地址相等, 则是找到
if (KeyInfoT::isEqual(Val, ThisBucket->first)) {
FoundBucket = ThisBucket;
return true;
}
//找到的桶中的 key 是空桶占位符, 则表示可插入
if (KeyInfoT::isEqual(ThisBucket->first, EmptyKey)) {
if (FoundTombstone) ThisBucket = FoundTombstone; //如果曾遇到墓碑, 则使用墓碑的位置
FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
return false; //找到空占位符, 则表明表中没有已经插入了该对象的桶
}
//如果找到了墓碑
if (KeyInfoT::isEqual(ThisBucket->first, TombstoneKey) && !FoundTombstone)
FoundTombstone = ThisBucket; // 记录下墓碑
//这里涉及到最初定义 typedef objc::DenseMap,size_t,true> RefcountMap, 传入的第三个参数 true
//这个参数代表是否可以清除 0 值, 也就是说这个参数为 true 并且没有墓碑的时候, 会记录下找到的 value 为 0 的桶
if (ZeroValuesArePurgeable &&
ThisBucket->second == 0 && !FoundTombstone)
FoundTombstone = ThisBucket;
//用于计数的 ProbeAmt 如果大于了数组容量, 就会抛出异常
if (ProbeAmt > NumBuckets) {
_objc_fatal("...");
}
BucketNo += ProbeAmt++; //本次哈希计算得出的下表不符合, 则利用 ProbeAmt 寻找下一个下标
BucketNo&= (NumBuckets-1); //得到新的数字和数组下标最大值按位与
}
}
weak_table_t weak_table
weak_table_t weak_table
用来存储OC对象弱引用的相关信息。我们知道,SideTables
一共只有64个节点,而在我们的APP中,一般都会不只有64个对象,因此,多个对象一定会重用同一个SideTable
节点,也就是说,一个weak_table
会存储多个对象的弱引用信息。因此在一个SideTable
中,又会通过weak_table
作为哈希表再次分散存储每一个对象的弱引用信息。
weak_table_t
是一个哈希表的结构, 根据对象的地址计算哈希值, 哈希值相同的对象按照下标 +1 的形式向后查找可用位置, 是典型的闭散列算法. 最大哈希偏移值即是所有对象中计算出的哈希值和实际插入位置的最大偏移量, 在查找时可以作为循环的上限.
/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*
* 全局若引用表,以objct为key,weak_entry_t做为值
*/
struct weak_table_t {
weak_entry_t *weak_entries; //hash数组,用来存储弱引用对象的相关信息weak_entry_t
size_t num_entries; // hash数组中的元素个数
uintptr_t mask; // hash数组长度-1,会参与hash计算。(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个)
uintptr_t max_hash_displacement; // 可能会发生的hash冲突的最大次数
};
通过对象的地址,可以在weak_table_t
中找到对应的 weak_entry_t
,weak_entry_t
中保存了所有指向这个对象的弱引用信息。
寻找的过程主要在weak_entry_for_referent()
函数中:
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; // 根据ref的地址获取hash值,然后和mask按位与运算,保证不越界
size_t index = begin;
size_t hash_displacement = 0; // hash冲突位移次数
while (weak_table->weak_entries[index].referent != referent) { // 循环判断weak_entry_t中的referent 是否与要找的referent相同
index = (index+1) & weak_table->mask; // 下标+1并和mask按位与运算,保证数组不越界
if (index == begin) bad_weak_table(weak_table->weak_entries); // 回到初始位置还没找到对应的referent,说明weak_table有问题,抛出异常
hash_displacement++; // 位移次数+1
if (hash_displacement > weak_table->max_hash_displacement) {
//位移超过hash冲突最大次数,说明没找到对应的weak_entry_t,返回空
return nil;
}
}
return &weak_table->weak_entries[index];
}
weak_entry_t
中使用了一个共用体, 当指向这个对象的weak
指针不超过 4 个, 则直接使用数组inline_referrers
,省去了哈希操作的步骤,如果weak
指针个数超过了 4 个,就要使用第一个结构体中的哈希表。第一个结构体的结构和weak_table_t
很像,同样也是一个哈希表,其存储的元素是weak_referrer_t
,实质上是弱引用该对象的指针的指针,即objc_object **new_referrer
, 通过操作指针的指针,就可以使得weak
引用的指针在对象析构后,指向nil
。
struct weak_entry_t {
DisguisedPtr referent; // 被引用的对象
// 引用该对象的弱引用共用体。
// 引用个数小于等于4,用inline_referrers数组。
// 引用个数大于4,用哈希数组weak_referrer_t *referrers
union {
struct {
weak_referrer_t *referrers; // 弱引用该对象的指针地址的哈希数组
uintptr_t out_of_line_ness : 2; // 是否使用动态哈希数组标记位
uintptr_t num_refs : PTR_MINUS_2; // 哈希数组中的元素个数
uintptr_t mask; // 哈希数组长度-1,会参与hash计算。(和weak_table_t的mask一样)。
uintptr_t max_hash_displacement; // 可能会发生的hash冲突的最大次数
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
// 弱引用数量不超过4个时存放弱引用指针的数组
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
的核心功能就是就是weak
指针的增加和删除功能,看一下增加功能--append_referrer()
/**
*
* 将给定的引用添加到entry中的弱指针集合中。不查重(b/c弱指针从不添加到集合两次)。
*
* @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)
{
if (! entry->out_of_line()) {
//判断out_of_line_ness标记,是否已经使用哈希数组
//如果还没使用就直接循环数组,找到空位置,把弱引用指针添加进去
// 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;
}
}
//如果数组中已经满了, 就要使用动态哈希数组referrers了
//从这里开始, 这一段是把inline_referrers数组调整为使用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.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
new_referrers[i] = entry->inline_referrers[i];
}
//配置weak_entry_t的参数
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());
//根据哈希数组的规则,使用量超过填装因子(一般0.7-0.8),就要扩容
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { // 数组使用量超过3/4
return grow_refs_and_insert(entry, new_referrer); //需要扩展数组并配置entry信息
}
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++; // 便宜次数+1
index = (index+1) & entry->mask; // 下标值+1,并与mask安位与运算
if (index == begin) bad_weak_table(entry); //如果找了一圈没找到空位置,说明这个entry有问题,抛出异常
}
if (hash_displacement > entry->max_hash_displacement) { //判断位移数是否大于原有的哈希冲突次数,如果超过就把新的偏移数重新赋值给哈希冲突数
entry->max_hash_displacement = hash_displacement;
}
// 把弱引用指针添加到referrers数组
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
// 引用次数+1
entry->num_refs++;
}
接着是删除功能--remove_referrer()
,基本上也和增加功能没什么区别:
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
if (! entry->out_of_line()) { //判断out_of_line_ness标记,是否已经使用哈希数组
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
// 循环判断,找到对应的ref,将其置空
if (entry->inline_referrers[i] == old_referrer) {
entry->inline_referrers[i] = nil;
return;
}
}
//如果循环一遍还没找到对应的弱引用,说明出bug了,抛出异常
_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;
}
// 使用hash数组的情况
size_t begin = w_hash_pointer(old_referrer) & (entry->mask); // 根据old_referrer 找到初始下标
size_t index = begin;
size_t hash_displacement = 0; // 哈希冲突位移次数
while (entry->referrers[index] != old_referrer) {
index = (index+1) & entry->mask; // 位移次数和mask按位与运算,保证不越界
if (index == begin) bad_weak_table(entry); //循环一圈未找到,说明有bug
hash_displacement++; // 位移次数+1
if (hash_displacement > entry->max_hash_displacement) {
// 如果位移次数超过了entry标记的最大冲突次数,说明有问题,抛出异常
_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--;
}
总结
调用storeWeak()
函数 --> 获取oldTable
、newTable
两个引用计数表 --> 调用weak_unregister_no_lock()
函数删除掉oldObj
的弱引用 --> 先调用weak_entry_for_referent()
找到弱引用信息表 --> 在调用remove_referrer()
删除弱引用信息 --> 调用weak_register_no_lock()
把新的弱引用信息添加到newObj
的弱引用表 --> 调用weak_entry_for_referent()
找到newObj
的弱引用信息表 --> 调用append_referrer()
把location
添加到弱引用表中 --> 把newObj
赋值给location