散列表结构分析
SideTable
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;//被__weak修改的对象会加到这里
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
......
};
weak_table
rootRetainCount
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) {
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
代码探索 - 弱引用表
NSObject *object = [[NSObject alloc] init];
NSLog(@"%zd - %@",(long)CFGetRetainCount((__bridge CFTypeRef)(object)),object);
__weak typeof(id) weakObject = object;
NSLog(@"%zd - %@",(long)CFGetRetainCount((__bridge CFTypeRef)(object)),object);
NSLog(@"%zd - %@",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObject)),weakObject);
NSObject *o1 = object;
NSLog(@"%zd - %@",(long)CFGetRetainCount((__bridge CFTypeRef)(o1)),o1);
/**
1 - <NSObject: 0x6000026a4230>
1 - <NSObject: 0x6000026a4230>
2 - <NSObject: 0x6000026a4230>
2 - <NSObject: 0x6000026a4230>
*/
从上面可以得出结论,使用弱引用的时候,不会对原来对象的引用计数发生改变. 引出的问题是:为什么weakobject 打印是2?
接下来探索,在__weak typeof(id) weakObject = object;
位置打断点,进入汇编调试,可以看到进入了objc_initWeak
:
objc_initWeak
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
//这里的location 是当前传进来的weakSelf的指针地址
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
可以得到结论,在出现__weak
的地方,会进入objc_initWeak
。这一点可以通过探索github上的llvm-project
,如下图:
storeWeak
template <HaveOld haveOld, HaveNew haveNew,
CrashIfDeallocating 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(cls, (id)newObj);
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_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;//外界的被弱引用的对象,即object
objc_object **referrer = (objc_object **)referrer_id;//创建的弱引用对象,即weakObject
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;
}
weak_entry_t
struct weak_entry_t {
DisguisedPtr<objc_object> referent;//将要被弱引用的对象,即object
union {
struct {
weak_referrer_t *referrers;//针对将要被弱引用对象生成的弱引用对象,即weakObject
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
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];
};
};
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;
}
}
};
小结
1、首先我们知道有一个 SideTable
2、得到SideTable
的weakTable
弱引用表
3、创建一个 weak_entry_t
4、把referent
加入到weak_table
的数组inline_referers
5、将weak_table
进行扩容
6、把new_entry
加入到weak_table
中
代码探索 - 弱引用的引用计数
NSObject *object = [[NSObject alloc] init];
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(object)),object,&object);
__weak typeof(id) weakObject = object;
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(object)),object,&object);
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObject)),weakObject,&weakObject);
NSObject *object1 = object;
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(object)),object,&object);
NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(object1)),object1,&object1);
/**
1 - <NSObject: 0x600000448090> - 0x7ff7bfc0e338
1 - <NSObject: 0x600000448090> - 0x7ff7bfc0e338
2 - <NSObject: 0x600000448090> - 0x7ff7bfc0e330
2 - <NSObject: 0x600000448090> - 0x7ff7bfc0e338
2 - <NSObject: 0x600000448090> - 0x7ff7bfc0e318
*/
探索弱引用表留下的问题依然没有解决,这时候接着分析,在NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObject)),weakObject,&weakObject);\
打断点,进入下面的方法:
那么查看 objc_loadWeakRetained
:
id
objc_loadWeakRetained(id *location)
{
id obj;
id result;
Class cls;
SideTable *table;
retry:
// fixme std::atomic this load
obj = *location;//weakObject
if (!obj) return nil;
if (obj->isTaggedPointer()) return obj;
table = &SideTables()[obj];
table->lock();
if (*location != obj) {
table->unlock();
goto retry;
}
result = obj;
cls = obj->ISA();
if (! cls->hasCustomRR()) {
// Fast case. We know +initialize is complete because
// default-RR can never be set before then.
ASSERT(cls->isInitialized());
//rootTryRetain
if (! obj->rootTryRetain()) {
result = nil;
}
}
else {
// Slow case. We must check for +initialize and call it outside
// the lock if necessary in order to avoid deadlocks.
if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
class_getMethodImplementation(cls, @selector(retainWeakReference));
if ((IMP)tryRetain == _objc_msgForward) {
result = nil;
}
else if (! (*tryRetain)(obj, @selector(retainWeakReference))) {
result = nil;
}
}
else {
table->unlock();
class_initialize(cls, obj);
goto retry;
}
}
table->unlock();
return result;
}
rootTryRetain => rootRetain
ALWAYS_INLINE **bool**
objc_object::rootTryRetain()
{
**return** rootRetain(**true**, **false**) ? **true** : **false**;
}
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return (id)this;
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
// don't check newisa.fast_rr; we already called any RR overrides
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (!handleOverflow) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
// Leave half of the retain counts inline and
// prepare to copy the other half to the side table.
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
经过上面代码的探索,可以得知,获取弱引用的引用计数的时候会依次走如下方法:
CFGetRetainCount
-> objc_loadWeakRetained
-> rootTryRetain
-> rootRetain
,在rootRetain
中会对引用计数加1,因此上面的引用计数为2的问题就有答案了。注意这次引用计数+1是临时的,当CFGetRetainCount
执行完毕,引用计数就-1.
弱引用表的管理与非弱引用表的管理是分开的。
辅助理解的图
上面两个断点依次会执行的内容:
关于timer的强引用
下面这句代码会造成循环引用
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
原因如下:
self -> timer -> self
runloop -> timer
扩展: 官方文档对于timerWithTimeInterval方法的target 参数解释:
The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to this object until it (the timer) is invalidated.
如何解决这种循环引用呢?
首先想到使用 weak
,因此尝试如下:
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
发现还是没有解决强引用的问题,原因如下:
self-> timer - weak -> self
runloop -> timer -> weak -> self
这里对weak会进行强引用。
而之前block与这个不同,block对weak是弱引用
self -> block -> weak -> self
解决方案一
- (void)didMoveToParentViewController:(UIViewController *)parent{
// 无论push 进来 还是 pop 出去 正常跑
// 就算继续push 到下一层 pop 回去还是继续
if (parent == nil) {
[self.timer invalidate];
self.timer = nil;
NSLog(@"timer 走了");
}
}
或者
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
// push 到下一层返回就不走了!!!
[self.timer invalidate];
self.timer = nil;
NSLog(@"timer 走了");
}
解决方案二
中介者模式 - 不方便使用 self 换其他对象
self.target = [[NSObject alloc] init];
class_addMethod([NSObject class], @selector(fireHome), (IMP)fireHomeObjc, "v@:");
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.target selector:@selector(fireHome) userInfo:nil repeats:YES];
void fireHomeObjc(id obj){
NSLog(@"%s -- %@",__func__,obj);
}
在中介者基础上进行优化:
self.timerWapper = [[LGTimerWapper alloc] lg_initWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
关于LGTimerWapper的源码,参考链接 LGTimerWapper
解决方案三
proxy
虚基类的方式
self.proxy = [LGProxy proxyWithTransformObject:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(fireHome) userInfo:nil repeats:YES];