Hash table structure analysis
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();
}
复制代码
Code Exploration - Weak Reference Table
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>
*/
复制代码
It can be concluded from the above that when using weak references, the reference count of the original object will not be changed. The question that arises is: why is weakobject print 2?
Next, explore, __weak typeof(id) weakObject = object;
break the point at the position, enter the assembly debugging, and you can see that you have entered 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);
}
复制代码
It can be concluded that, where it appears __weak
, it will enter objc_initWeak
. This can be explored by exploring github llvm-project
, as shown below:
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;
}
}
};
复制代码
summary
1. First we know that there is aSideTable
2. SideTable
The resulting weakTable
weak reference table
3. Create aweak_entry_t
4. referent
Add to weak_table
the arrayinline_referers
5. Will weak_table
be expanded
6. new_entry
Add weak_table
to
Code Exploration - Reference Counting for Weak References
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
*/
复制代码
The problem left by exploring the weak reference table is still unsolved. At this time, continue to analyze NSLog(@"%zd - %@ - %p",(long)CFGetRetainCount((__bridge CFTypeRef)(weakObject)),weakObject,&weakObject);\
and enter the following method at the break point:
Then check out 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;
}
复制代码
After the exploration of the above code, we can know that the following methods will be followed in order to obtain the reference count of the weak reference: CFGetRetainCount
-> objc_loadWeakRetained
-> rootTryRetain
-> rootRetain
, the rootRetain
reference count will be incremented by 1, so the problem of the above reference count of 2 is There is an answer. Note that the reference count +1 this time is temporary. When the CFGetRetainCount
execution is complete, the reference count will be -1.
The management of weak reference tables is separate from the management of non-weak reference tables.
diagrams to aid understanding
The above two breakpoints will execute in turn:
Strong reference to timer
The following code will cause a circular reference
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
复制代码
The reasons are as follows:
self -> timer -> self
runloop -> timer
复制代码
Extension: The official documentation explains the target parameter of the timerWithTimeInterval method:
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.
How to solve this circular reference?
First thought of using weak
, so try the following:
__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];
复制代码
It is found that the problem of strong references has not been solved for the following reasons:
self-> timer - weak -> self
runloop -> timer -> weak -> self
这里对weak会进行强引用。
而之前block与这个不同,block对weak是弱引用
self -> block -> weak -> self
复制代码
Solution one
- (void)didMoveToParentViewController:(UIViewController *)parent{
// 无论push 进来 还是 pop 出去 正常跑
// 就算继续push 到下一层 pop 回去还是继续
if (parent == nil) {
[self.timer invalidate];
self.timer = nil;
NSLog(@"timer 走了");
}
}
复制代码
or
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
// push 到下一层返回就不走了!!!
[self.timer invalidate];
self.timer = nil;
NSLog(@"timer 走了");
}
复制代码
Solution two
Mediator mode - inconvenient to use self for other objects
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);
}
复制代码
Optimizing on a mediator basis:
self.timerWapper = [[LGTimerWapper alloc] lg_initWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
复制代码
For the source code of LGTimerWapper, refer to the link LGTimerWapper
Solution three
proxy
virtual base class
self.proxy = [LGProxy proxyWithTransformObject:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(fireHome) userInfo:nil repeats:YES];
复制代码