weak
关键字的作用是弱引证,所引证方针的计数器不会加一,引证方针被开释时主动置nil
。
一、weak实现结构
SideTables
共有64个节点,每个节点是一个散列表(SideTable
),结构如下图
SideTable
SideTable
里边包含了一个自旋锁、一个引证计数表、一张weak
引证表
struct SideTable {
// 保证原子操作的自旋锁
spinlock_t slock;
// 引证计数的 hash 表
RefcountMap refcnts;
// weak 引证大局 hash 表
weak_table_t weak_table;
……
……
};
-
pinlock_t slock
:uint32_t
类型的非公正的自旋锁。非公正便是说取得锁的次序和申请锁的次序无关,第一个申请锁的线程可能最终才取得到该锁,或者是刚取得锁的线程会再次立刻取得到该锁,造成饥饿等待。在OC
中,_os_unfair_lock_opaque
也记载了获取它的线程信息,只有取得该锁的线程才能够解开这把锁。 -
RefcountMap refcnts
:用来存储方针的引证计数。它实质上是一个以objc_object
为key
的hash
表,其vaule
便是方针的引证计数。一起,当方针的引证计数变为0时,会主动将相关的信息从hash
表中除掉。 -
weak_table_t weak_table
: 用来存储OC方针弱引证的相关信息,详见下文
weak_table_t
-
weak_table
是典型hash
结构。 -
weak_entry_t *weak_entries
是一个动态数组,存储的是weak_entry_t
弱引证信息 - 每个
weak_entry_t
弱引证信息对应一个OC方针
struct weak_table_t {
// 弱引证信息动态数组
weak_entry_t *weak_entries;
// 数组重的元素个数
size_t num_entries;
// hash数组长度-1,会参与hash核算。
uintptr_t mask;
// 可能会产生的hash抵触的最大次数,用于判别是否呈现了逻辑过错(hash表中的抵触次数不会超越此值)
uintptr_t max_hash_displacement;
};
weak_entry_t
每个weak_entry_t
实例对应了一个OC方针
,内部有一个数组 objc_object **new_referrer
存储 weak_referrer_t
(指向弱引证该方针的指针),经过操作其指向能够使得weak
引证的指针在方针析构后,指向nil
。
详见注解
#define WEAK_INLINE_COUNT 4
#define REFERRERS_OUT_OF_LINE 2
#if __LP64__
#define PTR_MINUS_2 62
#else
#define PTR_MINUS_2 30
#endif
struct weak_entry_t {
// 弱引证方针指针地址取反
DisguisedPtr<objc_object> referent;
// 引证该方针的方针列表,联合。
// 弱引证该方针的指针数目小于等于 WEAK_INLINE_COUNT,运用定长数组 weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]。
// 弱引证该方针的指针数目超越 WEAK_INLINE_COUNT,将定长数组中的元素转移到动态数组 weak_referrer_t *referrers且之后都是用动态数组存储
// 为什么做定长/动态数组的切换?考虑到弱引证的指针个数一般不会超越`WEAK_INLINE_COUNT`个。这时候运用定长数组,不需求动态的申请内存空间,而是一次分配一块接连的内存空间。这会得到运转功率上的提高。
union {
struct {
// 弱引证该方针的方针指针地址的hash数组
weak_referrer_t *referrers;
// 是否运用动态hash数组符号位
uintptr_t out_of_line_ness : 2;
// hash数组中的元素个数
uintptr_t num_refs : PTR_MINUS_2;
// hash数组长度-1,会参与hash核算。
// ps:这儿是hash数组的长度,而不是元素个数。数组长度可能是64而元素仅存了2个。
uintptr_t mask;
// 可能会产生的hash抵触的最大次数,用于判别是否呈现了逻辑过错(hash表中的抵触次数不会超越此值)
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];
};
};
// 用来判别当时的是运用的定长数组还是动态数组
// 依据REFERRERS_OUT_OF_LINE注解:inline_referrers[1] 中存储的是 pointer-aligned DisguisedPtr,其最低位一定是 0b00 或 0b11 ,因而能够用 0b10 表示运用了动态数组
// 回来true,此刻运用的动态数组
// 回来false,运用静态数组
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的初始化
如下代码,创立一个NSObject
方针obj1
,生成另一个weak
标识的NSObject
方针obj2
指向obj1
objc_initWeak
断点进入汇编能够看到调用了objc_initWeak
办法
objc_initWeak
里边调用的是storeWeak
storeWeak
storeWeak
会先判别类是否初始化,假如没有则将其初始化然后retry
。接下来判别是否存在旧值需求删去,最终增加上新值。
storeWeak
太长就不贴图了,附上storeWeak
的代码和注解
// HaveOld: true - 变量有值
// false - 需求被及时整理,当时值可能为 nil
// HaveNew: true - 需求被分配的新值,当时值可能为 nil
// false - 不需求分配新值
// CrashIfDeallocating: true - 阐明 newObj 现已开释或者 newObj 不支持弱引证,该进程需求暂停
// false - 用 nil 替代存储
template <HaveOld haveOld, HaveNew haveNew,
enum CrashIfDeallocating crashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj)
{
// 该进程用来更新弱引证指针的指向
ASSERT(haveOld || haveNew);
if (!haveNew) ASSERT(newObj == nil);
// 初始化 previouslyInitializedClass 指针
Class previouslyInitializedClass = nil;
id oldObj;
// 声明两个 SideTable 新旧散列创立
SideTable *oldTable;
SideTable *newTable;
// 取得新值和旧值的锁存方位(用地址作为唯一标明)
// 经过地址来树立索引标志,避免桶重复
// 下面指向的操作会改变旧值
retry:
if (haveOld) { // 更改指针,取得以 oldObj 为索引所存储的值地址
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) { // 更改新值指针,取得以 newObj 为索引所存储的值地址
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
// 加锁操作,避免多线程中竞赛抵触
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
// 避免线程抵触重处理
// location 应该与 oldObj 保持一致,假如不同,阐明当时的 location 现已处理过 oldObj 但是又被其他线程所修改
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
// 避免弱引证间死锁
// 而且经过 +initialize 初始化结构器保证一切弱引证的isa不是空
if (haveNew && newObj) {
// 取得新方针的 isa 指针
Class cls = newObj->getIsa();
// 判别 isa 非空且现已初始化
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
// 解锁
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
// 对其 isa 指针进行初始化
class_initialize(cls, (id)newObj);
// 假如该类现已完成履行 +initialize 办法是最理想状况
// 假如该类 +initialize 在线程中
// 例如 +initialize 正在调用 storeWeak 办法
// 需求手动对其增加维护策略,并设置 previouslyInitializedClass 指针进行符号
previouslyInitializedClass = cls;
goto retry;
}
}
// ② 清除旧值
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// ③ 分配新值
if (haveNew) {
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
// 假如弱引证被开释 weak_register_no_lock 办法回来 nil
// 在引证计数表中设置若引证符号位
if (!newObj->isTaggedPointerOrNil()) {
// 弱引证位初始化操作
// 引证计数那张散列表的weak引证方针的引证计数中标识为weak引证
newObj->setWeaklyReferenced_nolock();
}
// 之前不要设置 location 方针,这儿需求更改指针指向
*location = (id)newObj;
}
else {
// 没有新值,则无需更改
}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
//必须在没有锁的状况下调用,由于它能够调用任意代码
//ps:即使_setWeaklyReferenced没有实现,resolveInstanceMethod:也可能被调用而且回调到弱引证机制中
callSetWeaklyReferenced((id)newObj);
return (id)newObj;
}
weak_register_no_lock
storeWeak
需求的是分配新值的操作weak_register_no_lock
。
该办法先记载下若引证的方针及其指针,保证弱引证方针有值而且不处于析构进程后,测验获取weak_table
中存储的旧实例weak_entry_t
。
- 假如存在旧实例就经过
append_referrer
办法将引证方针增加到旧实例 - 假如不存在就新建
weak_entry_t
并经过weak_entry_insert
刺进weak_table
中
weak_register_no_lock
源码解析
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{
// referent_id是新的被弱引证方针
objc_object *referent = (objc_object *)referent_id;
// referrer_id是__weak指针的地址
objc_object **referrer = (objc_object **)referrer_id;
// 假如referent为nil 或 referent 采用了TaggedPointer计数方法,直接回来,不做任何操作
if (referent->isTaggedPointerOrNil()) return referent_id;
// 保证被引证的方针可用(没有在析构,一起应该支持weak引证)
if (deallocatingOptions == ReturnNilIfDeallocating ||
deallocatingOptions == CrashIfDeallocating) {
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}
else {
// Use lookUpImpOrForward so we can avoid the assert in
// class_getInstanceMethod, since we intentionally make this
// callout with the lock held.
auto allowsWeakReference = (BOOL(*)(objc_object *, SEL))
lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference),
referent->getIsa());
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
}
// 正在析构的方针,不能够被弱引证
if (deallocating) {
if (deallocatingOptions == 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;
// 在 weak_table中找到referent对应的weak_entry,并将referrer加入到weak_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;
}
weak_entry_for_referent
那么怎么从weak_table
中定位到所需的 weak_entry_t
?
在weak_register_no_lock
里其实有调用到这个函数,便是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;
// 保证index不会越界,这和取余操作是相同的,用位操作替代能够提高功率。
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
// 开端比照hash表中的数据是否与方针数据相等
while (weak_table->weak_entries[index].referent != referent) {
// 假如不相等,则index +1,
index = (index+1) & weak_table->mask;
// index == begin 绕了一圈,抛出过错
if (index == begin) bad_weak_table(weak_table->weak_entries);
hash_displacement++;
// 超越了hash抵触最大值 return nil
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
return &weak_table->weak_entries[index];
}
接下来看看 weak_entry
是怎么刺进引证方针的
三、增加弱引证方针
在弱引证实例weak_entry
被创立以后,会判别弱引证表是否存在该实例。
- 假如存在就会走
append_referrer
- 不存在则创立后走
weak_entry_insert
两个函数处理类似,下面以append_referrer
为例做解析
append_referrer
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
if (! entry->out_of_line()) {
// 假如weak_entry 没有运用动态数组
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return;
}
}
// 假如inline_referrers的方位现已存满了,则要转型为referrers,做动态数组。
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());
// 假如动态数组中元素个数大于或等于数组方位总空间的3/4,则扩展数组空间为当时长度的一倍
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
// 扩容并刺进
return grow_refs_and_insert(entry, new_referrer);
}
// 开端刺进到weak_entry中
// 保证begin的方位只能大于或等于 数组的长度
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
// 初始的hash index
size_t index = begin;
// 用于记载hash抵触的次数,也便是hash再位移的次数
size_t hash_displacement = 0;
// 这儿 weak_entry_t 的hash算法和 weak_table_t 的hash算法是相同的,
while (entry->referrers[index] != nil) {
hash_displacement++;
// 移到下一个方位,再试一次能否刺进。
// 这儿entry->mask取值,一定是:0x111, 0x1111, 0x11111, ...
// 由于数组每次都是*2增长,即8, 16, 32,对应动态数组空间长度-1的mask,也便是前面的取值
index = (index+1) & entry->mask;
// 意味着走完都没有合适方位,抛出反常
if (index == begin) bad_weak_table(entry);
}
if (hash_displacement > entry->max_hash_displacement) {
// 记载最大的hash抵触次数
// max_hash_displacement是允许抵触的次数,假如超越都没方位便是出问题了
entry->max_hash_displacement = hash_displacement;
}
// 将ref存入hash数组
weak_referrer_t &ref = entry->referrers[index];
// 更新元素个数num_refs
ref = new_referrer;
entry->num_refs++;
}
至此,弱引证方针现已成功增加。
四、移除弱引证方针
接下来看看当方针被开释时,弱引证机制是怎么运转的。
rootDealloc
rootDealloc
逻辑如下:
首要判别 object
是否采用了 Tagged Pointer
计数,假如是则不进行任何析构操作。
接下来判别是否满足下列条件
- 方针采用了优化的
isa
计数方法(isa.nonpointer
) - 方针没有被
weak
引证(!isa.weakly_referenced
) - 没有相关方针(
!isa.has_assoc
) - 没有自定义的
C++
析构办法(!isa.has_cxx_dtor
) - 没有用到
sideTable
来做引证计数 (!isa.has_sidetable_rc
) 假如都满足则能用函数free(this)
快速开释内存。
其余的,则进入object_dispose((id)this)
慢开释分支。
object_dispose
调用 objc_destructInstance
objc_destructInstance
调用 clearDeallocating
clearDeallocating
调用clearDeallocating_slow
weak_clear_no_lock
clearDeallocating_slow
调用weak_clear_no_lock
整理weak_table
,一起将一切weak
引证置为nil
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
// 弱引证的方针
objc_object *referent = (objc_object *)referent_id;
// 获取弱引证实例
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()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
//遍历清空
for (size_t i = 0; i < count; ++i) {
//取出每个弱使用方针
objc_object **referrer = referrers[i];
if (referrer) {
//假如的确是要被开释的方针,置空
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_entry_remove(weak_table, entry);
}
至此,移除流程结束~
五、总结
最终,画了几张简图作为总结
底层结构
增加流程
移除流程