@qidiandasheng
2022-08-16T21:23:20.000000Z
字数 10878
阅读 3679
iOS理论
我们在学习iOS的时候常常会遇到weak
,而网上很多文章都有介绍过weak
,基本上有一句是最常见的。weak
为弱引用,不拥有对象,不增加对象的引用计数,当对象被释放后自动将指向对象的指针置为nil。但是我们要知其然而更知其所以然。下面我们来一起进入weak
的世界。
Runtime的源码是开源的我们先去Runtime源码地址下载源码,然后来一点一点对照分析。
简单点概括runtime实现weak属性就是:
对于注册为weak的对象,系统会把 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。
当我们初始化一个weak变量时,runtime会调用objc_initWeak
函数。这个函数在runtime源码中的NSObject.mm
文件中。具体定义如下:
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
(location, (objc_object*)newObj);
}
这里的两个参数location
表示weak
指针的地址,newObj
表示对象指针。我们看到当这个newObj
是个空指针或者其指向的对象已经被释放了,那么*location = nil;
,返回也是nil,则表示weak
的初始化其实是失败的。
然后如果成功的话执行storeWeak
函数并返回值。那我们来看看storeWeak
的定义:
template <bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
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(_class_getNonMetaClass(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;
}
我们可以看到里面有一个比较重要的类SideTable
,我们再来看看SideTable
的定义:
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
bool trylock() { return slock.trylock(); }
// Address-ordered lock discipline for a pair of side tables.
template<bool HaveOld, bool HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<bool HaveOld, bool HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
SideTable
里有一个RefcountMap refcnts;
和weak_table_t weak_table;
RefcountMap
里存储了一个对象的引用计数信息。而weak_table_t weak_table;
维护和存储了一个对象的所有弱引用的信息。具体的结构定义可以看runtime
源码中的objc-weak.h
文件。
知道了这些定义,我们现在就来看看整个storeWeak
函数的作用:
HaveOld
和HaveNew
来判断是否有新老对象。然后找到其新老对象,根据其新老对象获取与其相关的SideTable
对象。
if (HaveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (HaveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
然后就是一些锁的操作
最后就是在老对象的weak表中移除指向信息,而在新对象的weak表中建立关联信息,让弱引用指针指向新对象,然后返回这个新对象:
//移除老对象weak表中的信息
if (HaveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// 在新对象的weak表中建立关联
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();
}
// 弱引用指针指向新对象
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
//返回新对象
return (id)newObj;
当weak引用指向的对象被释放时,其基本流程如下:
其中objc_release
和_objc_rootDealloc
在NSObject.mm
中定义。
object_dispose
、objc_destructInstance
和clearDeallocating
在objc-runtime-new.mm
文件中定义。
我们看源码中的定义其实就能知道最后clearDeallocating
最关键的部分的是执行objc-weak.mm
文件中的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
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_t
链表,然后挨个将弱引用置为nil。最后清理对象的记录。
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
}
- spinlock_t slock : 自旋锁,用于上锁/解锁 SideTable。
- RefcountMap refcnts :用来存储OC对象的引用计数的 hash表(仅在未开启isa优化或在isa优化情况下isa_t的引用计数溢出时才会用到)。
- weak_table_t weak_table : 存储对象弱引用指针的hash表。是OC中weak功能实现的核心数据结构。
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
- weak_entries: hash数组,用来存储弱引用对象的相关信息weak_entry_t
- num_entries: hash数组中的元素个数
- mask:hash数组长度-1,会参与hash计算。(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个)
- max_hash_displacement:可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过改值)
weak_table_t
是一个典型的hash结构。weak_entries
是一个动态数组,用来存储weak_entry_t
类型的元素,这些元素实际上就是OC对象的弱引用信息。
weak_entry_t
的结构也是一个hash结构,其存储的元素是弱引用对象指针的指针, 通过操作指针的指针,就可以使得weak 引用的指针在对象析构后,指向nil。
#define WEAK_INLINE_COUNT 4
#define REFERRERS_OUT_OF_LINE 2
struct weak_entry_t {
DisguisedPtr<objc_object> referent; // 被弱引用的对象
// 引用该对象的对象列表,联合。 引用个数小于4,用inline_referrers数组。 用个数大于4,用动态数组weak_referrer_t *referrers
union {
struct {
weak_referrer_t *referrers; // 弱引用该对象的对象指针地址的hash数组
uintptr_t out_of_line_ness : 2; // 是否使用动态hash数组标记位
uintptr_t num_refs : PTR_MINUS_2; // hash数组中的元素个数
uintptr_t mask; // hash数组长度-1,会参与hash计算。(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个)素个数)。
uintptr_t max_hash_displacement; // 可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过改值)
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[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;
}
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[WEAK_INLINE_COUNT]
和动态数组weak_referrer_t *referrers
两种方式来存储弱引用对象的指针地址。通过out_of_line()
这样一个函数方法来判断采用哪种存储方式。当弱引用该对象的指针数目小于等于WEAK_INLINE_COUNT
时,使用定长数组。当超过WEAK_INLINE_COUNT
时,会将定长数组中的元素转移到动态数组中,并之后都是用动态数组存储。
我们知道weak
和unsafe_unretained
的不同之处是,设置weak属性后,系统会在对象被释放后自动将这个指向对象的指针设为nil。而unsafe_unretained
修饰的属性会在对象释放后产生悬空指针。
这里我们解释一下什么是空指针、悬空指针、野指针。
空指针:也就是weak修饰的指针,在对象释放后指针设为nil,也就成了空指针,在OC里给nil发送消息是不会崩溃的。
悬空指针:对象被释放后那块对象的内存已经失效了,此内存就是垃圾内存,但是还有指针指向这块垃圾内存,这个指针就是悬空指针。也就是unsafe_unretained
修饰的指针在对象释放后会成为悬空指针,给悬空指针发送消息是会崩溃的。
野指针:某些编程语言允许未初始化的指针的存在,而这类指针即为野指针。
1: 首先,创建一个ClassA类
2: 创建一个ClassB类,并在里面添加一个测试方法print,用于等下向ClassB实例对象发送消息,确认对象是否仍在使用
- (void)print {
NSLog(@"Object is %@", self);
}
3: 给ClassA添加一个ClassB对象属性,并设置为unsafe_unretained
@property (nonatomic, unsafe_unretained) ClassB *objectB;
4: 在main函数中添加如下代码:
@autoreleasepool {
ClassA *instanceA = [ClassA new];
ClassB *instanceB = [ClassB new];
instanceA.objectB = instanceB;
[instanceA.objectB print];
// release instanceB
instanceB = nil;
[instanceA.objectB print];
}
return 0;
5: 运行一下代码,不出意外的话,程序会挂掉,因为instanceA.objectB所指向的内存已经在instanceB = nil;时候被释放掉,instanceA.objectB仅指向一个悬空指针:
2016-03-09 15:25:10.427 WeakDemo[98402:1613532] Object is 0x7fa080d0f100
2016-03-09 15:25:10.432 WeakDemo[98402:1613532] Object is 0x7fa080d0f100
Process finished with exit code 139
当然,也有可能不会挂,这取决于执行print函数时,instanceB所在的内存是否完全释放掉。
6: 如果将ClassA中的属性改为@property (nonatomic, weak) ClassB *objectB;则不会出现crash,这就是weak的作用了,objectB对象如果被释放掉,则该指针变为nil,而向nil发送消息是不会出现问题的。
demo代码这里给出了具体的实现代码。具体的原理我简单说一下:
就是创建了一个
NSObject
的category
,category
里有一个方法传入一个block
并使用runtime Associate
方法关联一个delloc对象
,这个对象在delloc
的时候,会调用block
。所以当NSObjec
对象释放的时候,这个delloc对象
也就会执行block
,我们直接在block
里面设置指针为nil就不会产生悬空指针了。
这里有一个问题,我们给NSObject对象
关联的delloc对象
是在什么时候delloc
的呢?
我们看到对象销毁时流程是如下这样的:
在runtime
源码的objc-runtime-new.mm
文件中我们看到objc_destructInstance
的定义如下,可以看到里面有个_object_remove_assocations
执行了移除关联对象的操作:
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = !UseGC && obj->hasAssociatedObjects();
bool dealloc = !UseGC;
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
if (dealloc) obj->clearDeallocating();
}
return obj;
}
NSObject
的category
:NSObject+deallocBlock.h
@interface NSObject (deallocBlock)
- (void)runBlockOnDealloc:(voidBlock)block;
@end
@implementation NSObject (deallocBlock)
- (void)runBlockOnDealloc:(voidBlock)block {
if (block) {
DeallocBlock *deallocBlock = [[DeallocBlock alloc] initWithBlock:block];
objc_setAssociatedObject(self, _cmd, deallocBlock, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
}
@end
NSObjec
的category
中要关联的对象的类:DeallocBlock.h
typedef void (^voidBlock)();
@interface DeallocBlock : NSObject
@property(nonatomic, copy) voidBlock block;
- (instancetype)initWithBlock:(voidBlock)block1;
@end
@implementation DeallocBlock
- (instancetype)initWithBlock:(voidBlock)block1 {
self = [super init];
if (self) {
_block = [block1 copy];
}
return self;
}
- (void)dealloc {
if (_block) {
NSLog(@"DeallocBlock dealloc!");
_block();
}
}
@end
使用就是如下面代码这样,重写classA的setObjectB方法:
- (void)setObjectB:(ClassB *)objectB {
_objectB = objectB;
// 仅对objectB != nil case做处理
if (_objectB) {
[_objectB runBlockOnDealloc:^{
NSLog(@"_objectB dealloc");
_objectB = nil;
}];
}
}