iOS - weak 底层原理
[toc]
参考
https://blog.csdn.net/future_one/article/details/81606895
https://www.cnblogs.com/guohai-stronger/p/10161870.html
https://www.jianshu.com/p/2b12666b351f
问答
weak作用?
weak 是弱引用, 用weak来修饰、描述所引用对象的计数器并不会加1, 而且weak会在引用对象被释放的时候自动置为nil, 这也就避免了野指针访问坏内存而引起崩溃的情况,
weak也可以解决循环引用。
为什么修饰代理使用 weak 而不是用assign?
assign 可用来修饰基本数据类型, 也可修饰OC的对象, 但如果用 assign 修饰对象类型指向的是一个强指针, 当指向的这个指针释放之后, 它仍指向这块内存, 必须要手动给置为nil, 否则会产生野指针, 如果还通过此指针操作那块内存, 会导致 EXC_BAD_ACCESS 错误, 调用了已经被释放的内存空间;
而 weak 只能用来修饰OC对象, 而且相比assign比较安全, 如果指向的对象消失了, 那么它会自动置为nil, 不会导致野指针。
weak 本质?
ARC帮我们做了什么?
ARC 是 LLVM + Runtime 互相协作的结果
开启ARC后, LLVM编译器会自动帮我们在相应位置生成 release、retain、autorelease
在作用域 (大括号) 结束的位置添加 release
弱引用这种, 是通过runtime处理的
编译解析
OC代码
int main(){
NSObject *obj = [[NSObject alloc] init];
id __weak obj1 = obj;
}
C++代码
int main(){
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
id __attribute__((objc_ownership(weak))) obj1 = obj;
}
编译之后的weak, 通过 objc_ownership(weak)
实现 weak 方法, objc_ownership
字面意思是:获得对象的所有权, 是对对象weak的初始化的一个操作。
通过 weak 编译解析, 可以看出 weak 是通过 runtime 初始化并维护的;
weak 和 strong 都是 OC ARC 的修饰词, 而 strong 是通过 runtime 维护的一个自动计数表结构。
结构
weak_table_t
- weak_table_t 存储在
struct SideTable
中。(参考《isa指针》) - weak_table_t 是 Runtime 维护的一个 weak 引用的全局hash表, 用于存储指向某个对象的所有weak指针。★
- key: 所指向对象的地址
- value: weak_entry_t 类型结构体数组(weak指针的数组) 。 value 是数组的原因是: 因为一个对象可能被多个弱引用指针指向 ★
weak 指针的地址值是所指对象指针的地址
// objc-weak.h
/**
* The global weak references table. 全局的, 内部维护了一个哈希表 ★
* Stores object ids as keys, and weak_entry_t structs as their values.
*/
struct weak_table_t {
weak_entry_t *weak_entries; // 保存了所有指向指定对象的weak指针 weak_entries的对象
size_t num_entries; // 弱引用数量
uintptr_t mask;
uintptr_t max_hash_displacement;
};
weak_entry_t
weak 全局表 weak_table_t 中的存储 weak 定义的对象的表结构 weak_entry_t,
weak_entry_t 是存储在弱引用表中的一个内部结构体, 它负责维护和存储指向一个对象的所有弱引用hash表
// 单个弱引用, 存放在哈希表中
struct weak_entry_t {
// 对泛型对象的指针做了一个封装, 通过这个泛型类来解决内存泄漏的问题。
// 相当于 objc_object *referent;
DisguisedPtr<objc_object> referent;
union {
struct {
// weak_referrer_t 是 objc_object ** 的别名, 通过一个二维指针地址偏移, 用下标作为 hash 的 key, 做成了一个弱引用散列。
weak_referrer_t *referrers; // weak变量地址
uintptr_t out_of_line_ness : 2;
// 引用数值。这里记录弱引用表中引用有效数字, 因为弱引用表使用的是静态 hash 结构, 所以需要使用变量来记录数目。
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
// 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];
};
};
// 最低有效位, 也是标志位。当标志位 0 时, 增加引用表指针纬度。
// 当其为0的时候, weak_referrer_t 成员将扩展为多行静态 hash table。
// 通常情况下是等于零的, 所以弱引用表总是一个 objc_objective 指针二维数组。
// 一维 objc_objective 指针可构成一张弱引用散列表, 通过第三纬度实现了多张散列表, 并且表数量为 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;
}
// objc_object: weak_entry_t对象中的范型对象, 用于标示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;
}
}
};
#define WEAK_INLINE_COUNT 4
#define REFERRERS_OUT_OF_LINE 2
weak_referrer_t
// The address of a __weak variable.
// These pointers are stored disguised(伪装的) so memory analysis tools
// don't see lots of interior(内部的) pointers from the weak table into objects.
typedef DisguisedPtr<objc_object *> weak_referrer_t;
/* 苹果对 DisguisedPtr 的注释
DisguisedPtr<T> acts like pointer type T*, except the stored value is disguised to hide it from tools like `leaks`.
nil is disguised as itself so zero-filled memory works as expected,
which means 0x80..00 is also disguised as itself but we don't care.
Note that weak_entry_t knows about this encoding.
*/
可以看到 DisguisedPtr<objc_object *>
相当于 objc_object **
;
即 weak_referrer_t
就是 objc_object **
的别名
存储过程
初始化weak变量时, runtime会调用 objc_initWeak
函数, 初始化新的weak指针指向对象的地址;
objc_initWeak
函数内部会调用 objc_storeWeak()
函数, objc_storeWeak()
函数的作用是用来更新指针的指向, 创建弱引用表。
objc_initWeak
id objc_initWeak(id *location, id newObj) {
// 首先判断指针指向的类对象是否有效, 若无效直接释放指针并返回;
if (!newObj) {
*location = nil;
return nil;
}
// 注册为一个指向value的_weak对象
// 更新指针的指向, 创建弱引用表。
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
storeWeak()
// 更新指针指向, 创建对应弱引用表
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;
// 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;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
// 获取新引用散列
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
// 加锁操作, 解决选择竞争;
// 为了解决死锁问题, 可能会开启二次尝试
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
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.
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);
// 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.
}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
weak_unregister_no_lock()
/**
* Unregister an already-registered weak reference.
* This is used when referrer's storage is about to go away, but referent
* isn't dead yet. (Otherwise, zeroing referrer later would be a
* bad memory access.)
* Does nothing if referent/referrer is not a currently active weak reference.
* Does not zero referrer.
*
* FIXME currently requires old referent value to be passed in (lame)
* FIXME unregistration should be automatic if referrer is collected
*
* @param weak_table The global weak table.
* @param referent The object.
* @param referrer The weak reference.
*/
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))) {
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);
}
}
// Do not set *referrer = nil. objc_storeWeak() requires that the
// value not change.
}
weak_register_no_lock()
/**
* Registers a new (object, weak pointer) pair. Creates a new weak
* object entry if it does not exist.
*
* @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, 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,
@selector(allowsWeakReference));
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, @selector(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;
}
在最后会调用 clearDeallocating 函数。而 clearDeallocating 函数首先根据对象的地址获取weak指针地址的数组, 然后紧接着遍历这个数组, 将其中的数组开始置为nil, 把这个 entry从weak 表中删除, 最后一步清理对象的记录。
释放过程
当 weak
指向的对象被释放时, 编译器如何让 weak
指针置为nil的呢?
将弱引用存到一个弱引用表(哈希表)中, 对象销毁时, 就从表中取出当前对象的弱引用并清除
-
调用 objc_release
-
因为对象的引用计数为0, 所以执行 dealloc
-
在 dealloc 中, 调用了
_objc_rootDealloc
函数 -
在
_objc_rootDealloc
中, 调用了object_dispose
函数 -
调用
objc_destructInstance
-
最后调用objc_clear_deallocating,详细过程如下:
a. 从weak表中获取废弃对象的地址为键值的记录
b. 将包含在记录中的所有附有 weak修饰符变量的地址, 赋值为 nil
c. 将weak表中该记录删除
d. 从引用计数表中删除废弃对象的地址为键值的记录
释放过程 - 实验代码
@implementation Person
- (void)dealloc {
NSLog(@"%s", __func__);
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__strong Person *person1;
__weak Person *person2;
__unsafe_unretained Person *person3;
NSLog(@"111");
{
Person *person = [[Person alloc] init];
person2 = person;
person3 = person;
}
NSLog(@"222 - %@", person2); // 打印 null , 说明弱指针 person2 在其指向的对象销毁时, 会自动置空
NSLog(@"333 - %@", person3); // 会报坏EXC_BAD_ACCESS内存访问, 在这里person3依然有值, 只不过是个野指针, 指向的person对象已被销毁
}
@end
释放过程 - 源码
注: 关于 SideTable
、weak_table_t
、weak_entry_t
参考 《isa指针》
dealloc
// Replaced by CF (throws an NSException)
+ (void)dealloc {
}
// Replaced by NSZombies
- (void)dealloc {
_objc_rootDealloc(self);
}
rootDealloc()
void _objc_rootDealloc(id obj) {
ASSERT(obj);
obj->rootDealloc();
}
inline void objc_object::rootDealloc() {
if (isTaggedPointer()) return; // fixme necessary?
// 如果该对象是优化的isa(arm64)且 没有 弱引用 / 关联对象 / C++析构器 / 引用计数表, 直接释放
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc)) {
assert(!sidetable_present());
free(this);
}
// 普通isa不管有没有用过上述资源, 都走这里
// 需要释放该对象相关的其他资源
else {
object_dispose((id)this);
}
}
object_dispose()
// 有弱引用必然会走这里
id object_dispose(id obj) {
if (!obj) return nil;
// 释放 obj 相关资源
objc_destructInstance(obj);
// 释放自身
free(obj);
return nil;
}
objc_destructInstance()
// 释放 obj 相关资源
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); // 清除c++析构函数相关成员变量
if (assoc) _object_remove_assocations(obj); // 移除关联对象
// 根据当前对象的地址值, 找到对应的
obj->clearDeallocating(); // 将指向当前对象的 weak 弱指针置为nil ★
}
return obj;
}
clearDeallocating()
//
inline void objc_object::clearDeallocating() {
// 普通isa指针, 直接指向 class / metaclass
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
// arm64优化过的isa
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());
}
clearDeallocating_slow()
// arm64优化过的isa 走这里
NEVER_INLINE void objc_object::clearDeallocating_slow() {
ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
//
SideTable& table = SideTables()[this];
table.lock();
// 有弱引用 ★
if (isa.weakly_referenced) {
// SideTable 有个弱引用表 weak_table, 是个哈希表
// (SideTable 数据结构可到《isa》中查看)
weak_clear_no_lock(&table.weak_table, (id)this);
}
// 引用计数长度超出19bit, 存放在 SideTable
if (isa.has_sidetable_rc) {
// 清除引用计数
table.refcnts.erase(this);
}
table.unlock();
}
weak_clear_no_lock()
/**
* Called by dealloc; nils out all weak pointers that point to the
* provided object so that they can no longer be used.
*
* @param weak_table
* @param referent The object being deallocated.
*/
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);
}
weak_entry_for_referent()
/** 根据 要销毁的对象地址值 获取 弱引用入口
* Return the weak reference table entry for the given referent.
* If there is no entry for referent, return 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_entries = weak_table->weak_entries;
if (!weak_entries) return nil;
// 用 以要销毁的对象地址值 referent, 按位与 weak_table的掩码, 得出哈希表索引
// 可以看出, weak_table 内部有一个哈希表 weak_entries
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
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;
}
}
// 从哈希表中取出index对应的弱引用
return &weak_table->weak_entries[index];
}
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
// 释放weak变量地址, entry->referrers ★★
if (entry->out_of_line()) free(entry->referrers);
// 释放entry
bzero(entry, sizeof(*entry));
// 弱引用数量-1
weak_table->num_entries--;
weak_compact_maybe(weak_table);
}
// 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.
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
}
}
sidetable_clearDeallocating
// arm64之前的 普通isa 使用该方法释放
void objc_object::sidetable_clearDeallocating() {
SideTable& table = SideTables()[this];
// clear any weak table items
// clear extra retain count and deallocating bit
// (fixme warn or abort if extra retain count == 0 ?)
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
table.refcnts.erase(it);
}
table.unlock();
}