最近同事问了一句说:一个 weak
对象是怎么被释放的?什么时候会被释放?
我想了想说,就是把 SideTables
里 weak_table
散列表中持有这个对象的 referent
指针置为 nil
,这个 weak
对象就被释放了,具体释放时机是这个对象dealloc
的时候调用了 objc_destructInstance
时候检查散列表清理。
然后想了想,这里还是之前看过的,所以再回顾一下。
weak 的原理
创建类 Objc_Weak_Class
,简单实现一下:
// Objc_Weak_Class.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Objc_Weak_Class : NSObject
- (void)weakMethod;
@end
NS_ASSUME_NONNULL_END
// Objc_Weak_Class.m
#import "Objc_Weak_Class.h"
@implementation Objc_Weak_Class
- (void)weakMethod
{
Objc_Weak_Class *o = [[Objc_Weak_Class alloc] init];
__weak id weakPtr = o;
NSLog(@"weakMethod = %@",o);
}
@end
每次找不到怎么办的时候就汇编呗,万一能看到重要信息呢。(这个很重要,希望给看到这篇文章的读者们提供一种解决问题的思路。)
不知道怎么看汇编的朋友看这里,开启这个。
好,上面的准备工作结束了,我们给这里打个断点。(等程序运行到这里的时候,开启查看汇编)
汇编如下,汇编中出现 call xxx
,代表调用了一个 xxx
方法。
这里我们看到了3个重要方法调用,那就一点点分析吧。(objc_alloc_init
这个就不说明了)
objc_alloc_init
初始化一个对象[[cls alloc] init]
;objc_initWeak
初始化weak
的一些东西;objc_destroyWeak
销毁weak
的一些东西。
1、objc_initWeak
command + shift + o
搜索 objc_initWeak
(推荐使用这个搜索,有时候在xcode左上角的搜索搜不到),源码如下:
/**
* 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);
}
这里苹果还给了一段代码,告诉我们如何能出发这个方法的调用。
/**
* 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.)
,这里意思是说针对 weak
变量的修改不是线程安全的,但是清理 weak
是线程安全的。
为什么线程不安全,看完这些分析,就明白了。(写在了在最下方)
OK,继续进入 storeWeak
方法:
2、storeWeak
因为是 initWeak
,所以认为没有旧值,不影响我们分析,就是断点断不到旧值释放。
haveOld = flase
haveNew = ture
static id
storeWeak(id *location, objc_object *newObj)
{
//检查新旧对象必须存在一个
ASSERT(haveOld || haveNew);
//如果 haveNew == NO,判断是否 newObj != nil 崩溃
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;
//这里的 & 是引用的意思 "[oldObj]" 是对 '[]'的重载
//其实StripedMap是一个以void *p为key,PaddedT为value的的表。
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
//取出新对象的散列表
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
//散列表加锁
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
//如果有旧值,但是 *location != oldObj 代表可能有其他线程修改了,就从头来
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来防止弱引用机制和+initialize机制之间的死锁。
if (haveNew && newObj) {
Class cls = newObj->getIsa();
//如果没有初始化类,就初始化,容错代码
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
class_initialize(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 ? CrashIfDeallocating : ReturnNilIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
if (!newObj->isTaggedPointerOrNil()) {
//设置 obj 中 isa.weakly_referenced = true; 代表有弱引用
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);
// This must be called without the locks held, as it can invoke
// arbitrary code. In particular, even if _setWeaklyReferenced
// is not implemented, resolveInstanceMethod: may be, and may
// call back into the weak reference machinery.
//必须在没有锁的情况下调用,因为它可以调用任意代码。
//特别是,即使_setWeaklyReferenced没有实现,resolveInstanceMethod:也可能实现,也可能回调弱引用机制。
callSetWeaklyReferenced((id)newObj);
return (id)newObj;
}
看完上方的代码和注释,梳理一下流程:
- 1、以旧值和新值为key获取散列表对象
- 2、获取了散列表之后,判断如果
*location != oldObj
,可能发生线程争夺,重头开始 - 3、如果有旧值,释放旧值指针
- 4、向新值散列表中插入新值指针地址
- 5、设置弱引用
isa.weakly_referenced = ture
3、weak_entry_t 结构体说明
为了更好的理解下方的内容,先对 weak_entry_t
结构体进行说明。
struct weak_entry_t {
//弱引用对象
DisguisedPtr<objc_object> referent;
//联合体,公用一块内存
union {
//弱引用数组 weak_referrer_t 大于 4用这个
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; //最大哈希冲突值
};
//WEAK_INLINE_COUNT : 4
//弱引用数组 weak_referrer_t 小于等于 4用这个
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
//判断是否是用的 referrers 来存储弱引用指针
bool out_of_line() {
return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
}
//memcpy 复制内存指针方法
//覆盖老数据
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
存放了某个对象的所有弱引用指针,如果弱引用对象数量不超过四个就报错在结构体数组 inline_referrers
,否则保存在 referrers
。并且在使用inline_referrers
数组的内存会在 weak_entry_t
初始化的时候一并分配好,而不是需要用到的时候再去申请新的内存空间,从而达到提到运行效率的目的。
4、weak_unregister_no_lock
删除一个已经注册的弱引用。
**
* 取消注册一个已经注册的弱引用。
* 当referrer的存储即将消失,但referent还没有死时使用。(否则,稍后将referrer归零将是一个错误的内存访问。)
* 如果referent/referrer不是当前激活的弱引用,则不做任何操作。
* 不为0的引用。
*
* FIXME目前需要旧的参考值被传入(lame)
* 如果referrer被收集,FIXME将自动取消注册
*
* @param weak_table 全局弱表。
* @param referent 引用对象。
* @param referrer 弱引用。
*/
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
weak_entry_t *entry;
//如果传入的对象为空就返回
if (!referent) return;
//开始查找
//找打到了 entry 就处理
if ((entry = weak_entry_for_referent(weak_table, referent))) {
//从弱引用表中删除
remove_referrer(entry, referrer);
//用于判断是否这个entry内是否是空的
bool empty = true;
//如果使用了 weak_referrer_t *referrers 且 引用数 num_refs != 0 代表还有对象在用
if (entry->out_of_line() && entry->num_refs != 0) {
empty = false;
}
else {
//否则使用了 inline_referrers
//判断 inline_referrers 有没有值
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}
if (empty) {
//移除这个 entry
weak_entry_remove(weak_table, entry);
}
}
// Do not set *referrer = nil. objc_storeWeak() requires that the
// value not change.
}
1、weak_entry_for_referent
从上方代码看到了 weak_entry_for_referent
是去查找弱引用表中这个对象的 entry
,接着分析,源码如下:
/**
* Return the weak reference table entry for the given referent. //返回给定引用的弱引用表项。
* If there is no entry for referent, return NULL. //如果referent没有条目,返回NULL
* Performs a lookup. //执行查找
*
* @param weak_table
* @param referent The object. Must not be nil.
*
* @return The table of weak referrers to this object.
*/
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
ASSERT(referent);
//取出全局弱引用表中所有的 `weak_entry_t`
weak_entry_t *weak_entries = weak_table->weak_entries;
//如果全局弱引用表中没有弱引用对象就返回
if (!weak_entries) return nil;
//weak_table->mask 和 weak_table->max_hash_displacement 在 append_referrer 方法中复制了,看 append_referrer 即可。
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
//这里是在哈希查找
//直到在全局弱引用表里到了 referent 或者大于最大哈希查找冲突数 就返回
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];
}
2、remove_referrer
查找到了弱引用对象就要删除上一次保存的值,源码如下:
/**
* Remove old_referrer from set of referrers, if it's present.
* Does not remove duplicates, because duplicates should not exist.
*
* @todo this is slow if old_referrer is not present. Is this ever the case?
*
* @param entry The entry holding the referrers.
* @param old_referrer The referrer to remove.
*/
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
//如果使用的是 inline_referrers 就在这里找,并置为nil
if (! entry->out_of_line()) {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == old_referrer) {
//找到后在数组中置为nil
entry->inline_referrers[i] = nil;
return;
}
}
//因为调用 remove_referrer 表示这个 entry 被找到了,如果发现没有找到就 crash
_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;
}
//如果使用的是 referrers 就来到了这里
//这里和查找 entry 流程一样
size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (entry->referrers[index] != old_referrer) {
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
hash_displacement++;
//因为调用 remove_referrer 表示这个 entry 被找到了,如果发现没有找到就 crash
if (hash_displacement > entry->max_hash_displacement) {
_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;
//引用数减一
entry->num_refs--;
}
3、weak_entry_remove
从全局弱引用表中删除这个弱引用对象的数据,清空所占的内存空间。
/**
* Remove entry from the zone's table of weak references.
*/
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
// remove entry
//如果使用referrers 释放 referrers
if (entry->out_of_line()) free(entry->referrers);
// 以entry为起始地址的前sizeof(*entry)个字节区域清零
bzero(entry, sizeof(*entry));
//全局弱引用表,弱引用对象数量-1
weak_table->num_entries--;
//收缩表大小,不浪费空间
weak_compact_maybe(weak_table);
}
4、weak_compact_maybe
判断是否需要收缩表,原始全局散列表 old_size > 1024
并且全局散列表下的弱引用对象的个数 num_entries < old_size /16
才进行收缩。
// Shrink the table if it is mostly empty.
static void weak_compact_maybe(weak_table_t *weak_table)
{
size_t old_size = TABLE_SIZE(weak_table);
// Shrink if larger than 1024 buckets and at most 1/16 full.
//原始全局散列表 old_size > 1024 并且全局散列表下的弱引用对象的个数 num_entries < old_size /16 才进行收缩
if (old_size >= 1024 && old_size / 16 >= weak_table->num_entries) {
//收缩到原表的一半
weak_resize(weak_table, old_size / 8);
// leaves new table no more than 1/2 full
}
}
5、weak_resize
弱引用表重新生成方法,源码如下:
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;
//会重新 insert,恢复正确数据
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);
}
}
5、weak_register_no_lock
插入当前弱引用对象
/**
* Registers a new (object, weak pointer) pair. Creates a new weak
* object entry if it does not exist.
* 注册一个新的object 和 weak pointer的配对
* 创建一个新的弱对象条目(如果它不存在)
*
* @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, WeakRegisterDeallocatingOptions deallocatingOptions)
{
//要插入的对象
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
//如果是 isTaggedPointe 对象 或者 对象 = nil 直接返回
if (referent->isTaggedPointerOrNil()) return referent_id;
// ensure that the referenced object is viable
if (deallocatingOptions == ReturnNilIfDeallocating ||
deallocatingOptions == CrashIfDeallocating) {
bool deallocating;
//判断ISA->hasCustomRR(),
//此比特位会在该类或父类重写下列方法时
//retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference返回true
//通常状况咱们都不会重写这些方法,所以会返回false,取反就为true
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.
//使用lookupimportforward,
//这样我们就可以避免class_getInstanceMethod中的assert,
//因为我们故意在锁被持有的情况下调用这个callout。
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));
}
//如果被析构了,就crash,这里不允许当前对象被析构
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;
//查找弱引用对象
if ((entry = weak_entry_for_referent(weak_table, referent))) {
//查到了就拼接
append_referrer(entry, referrer);
}
else {
//没有查到
//初始化
weak_entry_t new_entry(referent, referrer);
//看看是否需要扩容, 4分之3定律
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;
}
1、weak_grow_maybe
如果大于等于 原始大小的4分之3就需要扩容,如果原始表不存在,就生成新表大小为64个字节
// 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) {
// 如果 old_size = 0 ,就扩容为 64
weak_resize(weak_table, old_size ? old_size*2 : 64);
}
}
2、weak_entry_insert
weak_entry_insert
方法中不判断全局弱引用表中是否存在当前对象直接插入。
这里和我们写代码思想是一样的,为了更好的性能和复用,业务逻辑在上层自己判断,下层只操作即可。
/**
* Add new_entry to the object's table of weak references.
* Does not check whether the referent is already in the table.
*/
/**
* 在对象的弱引用表中添加new_entry。
* 不检查referent是否已经在表中。
*/
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;
}
}
3、append_referrer
/**
* Add the given referrer to set of weak pointers in this entry.
* Does not perform duplicate checking (b/c weak pointers are never
* added to a set twice).
*
* @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)
{
//如果使用的是 inline_referrers ,找到一个空位就插入
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.
//如果 inline_referrers 没有空位了,那么就需要更换共用体中结构体的类型
//使用 weak_referrer_t *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.
//将 inline_referrers 赋值到 referrers
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;
//表示 referrers 启用 对应 out_of_lin()方法的调用
entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
//mask = 3
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);
}
//这里是更新当前 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 的弱引用数
entry->num_refs++;
}
6、callSetWeaklyReferenced
callSetWeaklyReferenced
调用 setWeaklyReferenced
方法设置对象的弱引用标志位为 ture
。
// Call out to the _setWeaklyReferenced method on obj, if implemented.
static void callSetWeaklyReferenced(id obj) {
if (!obj)
return;
Class cls = obj->getIsa();
if (slowpath(cls->hasCustomRR() && !object_isClass(obj))) {
ASSERT(((objc_class *)cls)->isInitializing() || ((objc_class *)cls)->isInitialized());
void (*setWeaklyReferenced)(id, SEL) = (void(*)(id, SEL))
class_getMethodImplementation(cls, @selector(_setWeaklyReferenced));
if ((IMP)setWeaklyReferenced != _objc_msgForward) {
(*setWeaklyReferenced)(obj, @selector(_setWeaklyReferenced));
}
}
}
1、setWeaklyReferenced_nolock
核心代码 newisa.weakly_referenced = true;
设置对象的弱引用标志位为 ture
。
inline void
objc_object::setWeaklyReferenced_nolock()
{
isa_t newisa, oldisa = LoadExclusive(&isa.bits);
do {
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
sidetable_setWeaklyReferenced_nolock();
return;
}
if (newisa.weakly_referenced) {
ClearExclusive(&isa.bits);
return;
}
//这里设置
newisa.weakly_referenced = true;
} while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
}
7、objc_destroyWeak
objc_destroyWeak
的流程和 objc_initWeak
一样只不过传入新对象为 nil
,只清理不插入。
void
objc_destroyWeak(id *location)
{
(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
(location, nil);
}
8、关于 weak 对象在 dealloc 时候的清理
调用顺序 dealloc
-> clearDeallocating
-> sidetable_clearDeallocating()
-> sidetable_clearDeallocating()
-> weak_clear_no_lock()
weak_clear_no_lock
源码如下:
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
//取联合体中保存的 referrers 和 count
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();
}
}
}
//移除这个 entry
weak_entry_remove(weak_table, entry);
}
9、关于 weak 线程不安全问题
我们把上述代码精简后,就能明白了:
objc_initWeak
初始化weak
的一些东西;objc_destroyWeak
销毁weak
的一些东西。
objc_initWeak
:
id objc_initWeak(id *location, id newObj)
{
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
objc_destroyWeak
:
void objc_destroyWeak(id *location)
{
(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
(location, nil);
}
storeWeak
:
static id
storeWeak(id *location, objc_object *newObj)
{
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
retry:
//...
//这里的代码是取 oldTable 和 newTable
//散列表加锁
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
//...
///取消注册一个已经注册的弱引用。
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
//...
//插入新的 location
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
//...
//释放锁
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
callSetWeaklyReferenced((id)newObj);
return (id)newObj;
}
上述代码能看到 weak
的初始化 (objc_initWeak
) 和销毁 (objc_destroyWeak
)都调用了 storeWeak
, 在上方分析 weak_unregister_no_lock
和 weak_register_no_lock
的时候发现这两个方法内都是对指针地址的操作,但是方法内并没有进行加锁,所以在多线程操作的情况下,只是通过锁定地址来锁定排序问题,并没有对指针操作进行保护,所以这里是初始化和销毁 weak
为线程不安全。
10、补充一个图
这个图能看出通过 weak_table
的结构如下:
到这里 weak 的原理探析 就结束了,有问题欢迎大佬指出,共同学习,不断进步。
PS:可以运行的并且不断进行注释的objc 源码地址。