weak
是iOS
开发中很常见的知识点,大家都知道weak
是一个修饰词,作用是对修饰的对象弱引用,在对象被释放的时候引用计数会置空。
weak
创建流程
在main
函数写入以下代码,设置一个断点,然后进入汇编
int main(int argc, const char * argv[]) {
@autoreleasepool {
id __weak objc = object;
}
return 0;
}
然后我们设置一个符号断点,objc_initWeak
,会进入以下代码:
id objc_initWeak(id *location, id newObj)
{
// 判断所引用的对象是否存在
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak
(location, (objc_object*)newObj);
}
这个方法有两个参数:
-
location
:指__weak
指针的地址,它是一个指针的地址。之所以要存储指针的地址,是因为当我们释放对象的时候需要将__weak
指针指向的内容置为nil
。 -
newObj
:所引用的对象。
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;
......
retry:
if (haveOld) {
// 如果已经存在引用
// 给oldObj赋值原来的指针地址
// 然后根据oldObj取出之前的散列表oldTable
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
// 如果不存在之前的引用 则将oldTable置为nil
oldTable = nil;
}
if (haveNew) {
// 根据newObj取出散列表newTable
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
// 加锁
SideTable::lockTwo(oldTable, newTable);
if (haveOld && *location != oldObj) {
SideTable::unlockTwo(oldTable, newTable);
goto retry;
}
if (haveNew && newObj) {
// 有新对象而且可以存新的weak引用
// 判断新对象的类是否已经初始化
// 没有初始化就将其初始化
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;
}
}
// Clean up old value, if any.
if (haveOld) {
// 如果有旧的引用,调用weak_unregister_no_lock清除
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
if (haveNew) {
// 有新的引用需要设置,调用weak_register_no_lock注册引用
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.
// 设置isa指针的weakly_referenced标志位
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// 将新值放入location的地址位
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
SideTable::unlockTwo(oldTable, newTable);
return (id)newObj;
}
-
HaveOld
:weak
指针之前是否指向了一个弱引用 -
HaveNew
:weak
指针是否指向一个新的弱引用
该方法主要是在存储weak
指针的时候,判断之前是否有过弱引用,有的话调用weak_unregister_no_lock
方法清除;然后在判断是否要存储新的弱引用,需要的话,调用weak_register_no_lock
注册存储引用。
注册弱引用
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;
......
// now remember it and where it is being stored
weak_entry_t *entry;
// 从weak散列表找当前对象的引用数组
if ((entry = weak_entry_for_referent(weak_table, referent))) {
// 找到了将新的引用直接拼接到表中
append_referrer(entry, referrer);
}
else {
// 没找当前对象的引用数组 创建了这个数组 - 插入weak_table
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
}
return referent_id;
}
上述方法注册了一个新的对象和weak
指针,如果没有的话就会创建一个新的weak object entry
。参数如下:
-
weak_table_t *weak_table
:存储所有weak
引用的散列表 -
id referent_id
: 需要引用的对象指针 -
id *referrer_id
: 弱引用指针的地址 -
bool crashIfDeallocating
:如果引用正在释放的时候是否会崩溃
该方法中引入了两个非常关键的东西:
-
weak_table_t
: 存储所有weak
引用的散列表 -
weak_entry_t
: 存储每一个weak
引用相关信息结构的数组
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
weak_register_no_lock
主要实现的功能是如果对象可以被弱引用,则将被弱引用对象所在的weak_table
中的weak_entry_t
哈希数组中取出对应的weak_entry_t
,然后将对象和弱引用指针插入weak_entry_t
;如果weak_entry_t
不存在,则会新建一个weak_entry_t
数组,然后将将对象和弱引用指针插入创建的weak_entry_t
数组,再把weak_entry_t
数组插入weak_table
表中。
插入弱引用值
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
if (! entry->out_of_line()) {
// 先往`inline`数组里插入
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return;
}
}
// 如果不能插入inline数组,初始化一个新数组,将inline数组里面的元素插入新数组
weak_referrer_t *new_referrers = (weak_referrer_t *)
calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
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_entry_t *entry
:存储当前对象所有weak pointers
的集合 -
objc_object **new_referrer
:需要插入的新引用指针
该方法将给定的引用往weak_entry_t
的weak
指针集合里面插入,先往inline
里存放,如果inline
存放不下,则会创建一个新的数组referrers
,然后inline
里的元素插入referrers
,当referrers
容量不够时,会对其进行扩容操作,数组里会自动去重。
反注册弱引用
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;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
// 如果从weak_table表中获取到引用数组
// 删除原来的引用
// 如果引用数组里没有数据,则将引用数组从weak_table里移除
remove_referrer(entry, referrer);
bool empty = true;
if (entry->out_of_line() && entry->num_refs != 0) {
empty = false;
}
else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}
if (empty) {
weak_entry_remove(weak_table, entry);
}
}
}
该方法用来反注册一个已经注册的weak
引用,也就是清除注册。
-
weak_table_t *weak_table
:存储weak
的散列表 -
id referent_id
:引用的对象 -
id *referrer_id
:weak
引用的地址
weak
释放
跳出作用域的时候,弱引用就会释放对象,那么它是怎么释放对象的呢?下面我们来查看一下流程:
- (void)dealloc {
_objc_rootDealloc(self);
}
void
_objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
// 清除weak表中的相关引用
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
// 通过引用对象从weak_table里找到对应的weak_entry_t
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;
// 从inline_referrers和referrers查找对应的引用指针
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) {
// 将引用指针置为nil
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
objc_weak_error();
}
}
}
weak_entry_remove(weak_table, entry);
}
通过代码,我们可以看出,当跳出作用域的时候,先从在散列表中找到存储弱引用的散列表weak_table
,通过引用指针找到引用对象,然后再通过引用对象从weak_table
里找到对应的weak_entry_t
,
- 先在
weak_entry_t
的referrers
里面查找,找到就将对应的引用指针地址置为nil
- 找不到就在
inline_referrers
里查找,然后将对应的引用指针地址置为nil
总结:
创建弱引用的过程:
-
- 判断当前对象是否存在弱引用,是否需要添加新的弱引用
-
- 通过
SideTable
找到存储弱引用的weak_table
- 通过
-
- 存在弱引用,对其执行反注册,清除操作
-
- 需要添加新的弱引用,则根据
referent
弱引用对象,找weak_entry_t
- 找到,将弱引用指针插入
weak_entry_t
。append_referrer(entry, referrer)
- 找不到,新建一个
weak_entry_t
,写入弱引用指针,然后把新建的weak_entry_t
插⼊到weak_table
- 需要添加新的弱引用,则根据
释放weak
当出了作用域的时候,会将作用域内弱引用的对象进行释放,释放的关键点在于将通过引用对象找到的弱引用表weak_table
中的引用指针置为nil
、将引用指针地址置空。