objc-weak 阅读
内容来源于: objc4-750—> objct-weak.h/mm
此文件主要是实现管理对象的弱引用关系, 内部是通过一个SideTable来管理所有object的weak引用, 当该object引用技术为0被销毁时, 对象也会随之从引用表中删除. 通过弱引用机制, 可以避开对象的引用计数无法清0, 的循环引用(比较常见的例子是delegate之间的互相依赖)导致内存泄漏。
object-weak.h
这个文件主要定义了 weak_entry_t
和 weak_table_t
以及weak对象的注册方法, 将对象的Id作为key存储, 并将引用他们的对象信息存储到 weak_table_t
中,
- 全局的weak引用计数表, 用来存储弱引用对象的hash tab
struct weak_table_t {
weak_entry_t *weak_entries; //weak引用实体的记录,内部通过 `inline_referrers` 记录所有持有的弱医用指针
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
- 将 对象的weak指针和对象本身以key-value的形式插入或从hash tab中删除
id weak_register_no_lock(weak_table_t *weak_table, id referent, id *referrer, bool crashIfDeallocating);
void weak_unregister_no_lock(weak_table_t *weak_table, id referent, id *referrer);
void weak_clear_no_lock(weak_table_t *weak_table, id referent);
- weak指针记录, 通过
inline_referrers
记录了外部的weak指针的引用
struct weak_entry_t {
//对objc_object指针重新包装,避免被系统内存检测工具误测
DisguisedPtr referent;
//拷贝一个新的 `weak_entry_t`
weak_entry_t& operator=(const weak_entry_t& other) {
memcpy(this, &other, sizeof(other));
return *this;
}
//当引用更新时候,重新替换,并将原来的关联的weak指针全部释放掉
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指针到hash tab中
系统在注册时针对优化后的指针对象 TaggedPointer
的weak引用不做记录,因为该指针本身就保存对象的值
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
//对象的指针
objc_object *referent = (objc_object *)referent_id;
//实例对象
objc_object **referrer = (objc_object **)referrer_id;
//主要对指针存储值的类型进行过滤
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;
}
weak引用的使用
主要分为以下几种: 创建weak指针, 获取weak指针, 拷贝weak指针, 销毁weak指针
- 在Objective-C中创建一个弱引用指针的时候(
__weak typeof(object) weakObject = object;
), 会调用此方法将存储该weak指针, 如果weak指针引用为空, 则不会做处理
/**
* 初始化一个weak指针
* It would be used for code like:
*
* (The nil case)
* __weak id weakPtr;
* (The non-nil case)
* NSObject *o = ...;
* __weak id weakPtr = o;
*
* 此函数不是线程安全的, 因此后面在storeWeak方法内部进行了加锁
*
* @param location Address of __weak ptr.
* @param newObj Object ptr.
*/
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak
(location, (objc_object*)newObj);
}
/**
* @param location weak指针的地址, 代表__weak申明的变量
* @param obj weak指针引用的地址, 上面等号后面的具体的object实例
*/
OBJC_EXPORT id _Nullable
objc_storeWeak(id _Nullable * _Nonnull location, id _Nullable obj)
OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0);
- 在任何地方通过
__weak
引用对象执行的表达式,根据传入的对象返回一个weak引用的指针,确保它的引用计数不受影响
OBJC_EXPORT id _Nullable
objc_loadWeak(id _Nullable * _Nonnull location)
OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0);
- 此外还有
objc_moveWeak
,objc_copyWeak
,objc_destroyWeak
, 通过clange rewrite obc
, 再从编译后的代码反查就知道对应的实际了.
weak引用的hashTable是怎么创建的
- 首先需要创建一个全局的
SideTables
, 用于持有所有的SideTable
,SideTables
内部是由SideTableBuf
定义的数组, 每个元素占8位置,
//要想创建
static StripedMap& SideTables() {
return *reinterpret_cast*>(SideTableBuf);
}
//系统不能使用C++静态初始化器初始化sidabess,因为
//在C++初始化器运行之前调用我们。我们也不希望使用一个全局的指针指向此结构的指针,因为存在额外的间接寻址。
alignas(StripedMap) static uint8_t
SideTableBuf[sizeof(StripedMap)];
static void SideTableInit() {
new (SideTableBuf) StripedMap();
}
- 当在存储新的 weak指针和weak指针指向的object时,最终会以key-value的形式存储他们
newTable = &SideTables()[newObj];
, 对象的地址会作为key,每个对象会有很多个weak指针需要保存,所以需要为它再创建一个索引表SideTable
.
storeWeak(id *location, objc_object *newObj) {..
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
//此处用到了c的 `goto` 跳转语句并配合 `SideTable` 中自旋锁要一起使用,更新当前object的weak指针的索引表
retry:
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
SideTable::lockTwo(oldTable, newTable);
if (haveOld && *location != oldObj) {
SideTable::unlockTwo(oldTable, newTable);
goto retry;
}
//防止弱引用间死锁,确保引用的对象执行了被引用的对象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));
previouslyInitializedClass = cls;
goto retry;
}
}
//当对象销毁是,清空对应的hash tab的weak指针
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
if (haveNew) {
//对象的弱引用指针最终被存储到SidleTab下的 `weak_table` 中,crashIfDeallocating指定当对象被销毁时是否需要抛出异常,这里主要是针对线程不安全的情况设计
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.
}
//解锁返回新的weak指针
SideTable::unlockTwo(oldTable, newTable);
return (id)newObj;
}
-
SideTable
主要控制了wak
struct SideTable {
spinlock_t slock; //自旋锁,用于对SideTable的修改做加锁操作
RefcountMap refcnts;
weak_table_t weak_table;
//通过调用 `memory set` 方法,初始化一个一个空格weakTable
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.
template
static void lockTwo(SideTable *lock1, SideTable *lock2);
template
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
小结
Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。
Objective-C为了解决对象之间引用计数带来的循环引用问题, 增加了 __weak
关键字, 弱引用机制, 通过它来实现对象之间的简介引用, 不增加引用计数也能访问到对象, 避免造成资源上的死锁. 通常在 闭包和属性传值(常见了有delegate)中容易出现问题, 通过 __weak
指针就能解除这种互相等待造成的思索问题.
Runtime为每个带weak引用的对象分配一个SideTable,保存在全局的SidleTables中,它们的关系如下(SideTables(以object地址为key, 并为其创建一个SideTable作为value) -> SideTable(加锁后更新weak_table) -> weak_table_t(存储weak指针))
, 第一次初始化weak对象的的时候会创建一个SideTable, 并通过SideTab中的 weak_table_t
来存储当前对象所对应的所有weak指针, SideTable存放在一个全局的 SideTabs
中. 每个带有weak引用的object都有一个自己专属的SideTab, 它们以key-value的形式存储。key为对象自己本身, value则是在不同的地方定义的对该对象的引用指针, 在对象释放的时候会将该对象对应的SideTable清空.