The core idea of Objective-C memory management is to control the life cycle of memory objects by reference counting objects. To put it bluntly, a call to retain is incremented by 1, a call to release is decayed by 1, the reference count is cleared, or a call to dealloc is destroyed.
Reference counting
The reference count is the number of times an object is held. Is the core of memory management. Let’s look at an example of reference counting:
- (void)testRefCount {
NSObject *obj = [NSObject alloc];
NSLog(@"==refCount==%ld==", (long)CFGetRetainCount((__bridge CFTypeRef)(obj)));
}
Copy the code
Run the program and the result is 1. There is no reference counting operation in the alloc process, so why is the print 1? RetainCount:
- (NSUInteger)retainCount {
return ((id)self)->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();
}
Copy the code
As you can see, the total value of the reference count is the extra_rc value in the ISA plus the reference count table in the hash table plus 1, and for our new alloc object, the reference count is printed as 1 because of this 1, but the new alloc object has a reference count of 0.
So where are reference counts stored, and how are retain and release handled? Let’s take a look at retain:
retain
id objc_retain(id obj) { if (! obj) return obj; if (obj->isTaggedPointer()) return obj; return obj->retain(); } inline id objc_object::retain() { assert(! isTaggedPointer()); if (fastpath(! ISA()->hasCustomRR())) { return rootRetain(); } return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain); }Copy the code
As you can see, calling objc_retain will first determine if it is isTaggedPointer and return it if it is. RootRetain is then called if the object does not have a custom retain/release method, otherwise a SEL_retain message is sent via objc_msgSend.
id objc_object::rootRetain() { return rootRetain(false, false); } 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; Isa oldisa = LoadExclusive(&ISa.bits); newisa = oldisa; if (slowpath(! Newisa.nonpointer)) {// Not nonpointer ISA hash table handles ClearExclusive(& ISa.bits); if (! tryRetain && sideTableLocked) sidetable_unlock(); if (tryRetain) return sidetable_tryRetain() ? (id)this : nil; else return sidetable_retain(); } // If deallocating is not being done, slowPath (tryRetain && newisa.deallocating) {ClearExclusive(&isa.bits); if (! tryRetain && sideTableLocked) sidetable_unlock(); return nil; } // is nonpointer ISA extra_rc++ 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); } // Store half of it in the hash table, half in the extra_rc and process the EXTRA_RC flag bits of ISA and has_sideTABLE_rc 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)) { sidetable_addExtraRC_nolock(RC_HALF); } if (slowpath(! tryRetain && sideTableLocked)) sidetable_unlock(); return (id)this; }Copy the code
Enter rootRetain and do the following:
-
- Determine whether or not
isTaggedPointer
.TaggedPointer
Does not need to maintain reference counting, returns directly.
- Determine whether or not
-
- not
TaggedPointer
Gets the object’sisa
, judge whether or notnonpointer isa
- not
-
- not
nonpointer isa
, to hash table processing, reference counting++
Operation, and then return
- not
-
- To determine whether or not
deallocating
If yes, return directly
- To determine whether or not
-
- is
nonpointer isa
forisa
The mark of aextra_rc
perform++
operation
- is
-
- If the count exceeds
extra_rc
As many as you can store, you store half of themextra_rc
And put thehas_sidetable_rc
Flag position is 1. Then copy the other half and save it in the hash table.
- If the count exceeds
Hash tables are multiple tables, multiple tables rather than one for performance and security reasons, but multiple tables are not just one table per object. Hash tables store reference counts as follows:
bool objc_object::sidetable_tryRetain() { SideTable& table = SideTables()[this]; bool result = true; RefcountMap::iterator it = table.refcnts.find(this); if (it == table.refcnts.end()) { table.refcnts[this] = SIDE_TABLE_RC_ONE; } else if (it->second & SIDE_TABLE_DEALLOCATING) { result = false; } else if (! (it->second & SIDE_TABLE_RC_PINNED)) { it->second += SIDE_TABLE_RC_ONE; } return result; } id objc_object::sidetable_retain() { #if SUPPORT_NONPOINTER_ISA assert(! isa.nonpointer); #endif SideTable& table = SideTables()[this]; table.lock(); size_t& refcntStorage = table.refcnts[this]; if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) { refcntStorage += SIDE_TABLE_RC_ONE; } table.unlock(); return (id)this; }Copy the code
release
Retain is reference count +1 and release is reference count -1, and the flow is complementary. Calling release will still get you into the objc_Release method.
void objc_release(id obj) { if (! obj) return; if (obj->isTaggedPointer()) return; return obj->release(); } inline void objc_object::release() { assert(! isTaggedPointer()); if (fastpath(! ISA()->hasCustomRR())) { rootRelease(); return; } ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_release); }Copy the code
As you can see, calling objc_Release also checks if it is isTaggedPointer and returns it if it is. RootRelease is then called if the object does not have a custom retain/release method, otherwise the SEL_release message is sent via objc_msgSend.
bool objc_object::rootRelease() { return rootRelease(true, false); } objc_object::rootRelease(bool performDealloc, Bool handleUnderflow) {if (isTaggedPointer()) return false; bool sideTableLocked = false; isa_t oldisa; isa_t newisa; retry: do { oldisa = LoadExclusive(&isa.bits); newisa = oldisa; if (slowpath(! Newisa.nonpointer)) {// Not nonpointer_ISA handles the reference count in the hash table ClearExclusive(& ISa.bits); if (sideTableLocked) sidetable_unlock(); return sidetable_release(performDealloc); } // nonpointer_isa uses the uintptr_t carry in the EXTRA_rc of ISA; newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); If (slowpath(carry)) {// If (slowpath(carry)) {// If (slowpath(carry)) {// If (slowpath(carry)); } } while (slowpath(! StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits))); if (slowpath(sideTableLocked)) sidetable_unlock(); return false; underflow: newisa = oldisa; If (slowPath (newisa.has_sidetable_rc)) {if (! handleUnderflow) { ClearExclusive(&isa.bits); RootRelease_underflow (performDealloc); rootRelease_underflow(performDealloc); } if (! sideTableLocked) { ClearExclusive(&isa.bits); sidetable_lock(); sideTableLocked = true; // The hash table is not unlocked, and the lock is recursive goto retry; Size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF); If (borrowed > 0) {// If (borrowed > 0) {// Do the -- operation on the reference count of the hashtable and store it in the EXTRA_rc of ISA. bool stored = StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits); if (! Stored) {// If you did not successfully store into ISA's extra_rc then store again at...... } if (! Stored) {sidetable_addExtraRC_nolock(borrowed); // Stored) {sidetable_addExtraRC_nolock(borrowed); goto retry; } // Borrow from the hash table -- success sidetable_unlock(); return false; } else {// The reference count for the hash is empty}} // ISA didn't throw an exception in deallocating if (slowPath (newisa.deallocating)) { ClearExclusive(&isa.bits); if (sideTableLocked) sidetable_unlock(); return overrelease_error(); } // Mark ISA as deallocating, and recurse newisa.deallocating = true; if (! StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; if (slowpath(sideTableLocked)) sidetable_unlock(); __sync_synchronize(); If (performDealloc) {// Send a SEL_dealloc message ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); } return true; }Copy the code
To comb through the process, enter rootRelease as follows:
-
- Determine whether or not
isTaggedPointer
.TaggedPointer
Does not need to maintain reference counting, returns directly.
- Determine whether or not
-
- not
TaggedPointer
Gets the object’sisa
, judge whether or notnonpointer isa
- not
-
- not
nonpointer isa
, to hash table processing, reference counting--
If the reference count of the hash table is cleared, the object needs to be sentSEL_dealloc
Information, executiondealloc
Operation, and then return.
- not
-
- If it is
nonpointer_isa
onisa
theextra_rc
for--
Operation, whenextra_rc
When the count is 0 and not enough subtraction is required, the subtraction is borrowed from the hash table reference counter.
- If it is
-
- judge
isa
thehas_sidetable_rc
If there is a value, go to step 6, if there is no value, go to step 8
- judge
-
- Gets the reference count for the hash table, and if the count is zero, sends it to the object
SEL_dealloc
Information, executiondealloc
Operation.
- Gets the reference count for the hash table, and if the count is zero, sends it to the object
-
- If the reference count obtained from the hash table is greater than zero, subtract one from the count and store
isa
theextra_rc
And return. If there is no success, the recursive store is done twice, and if there is still no success, the count is stored in the hash table and one more recursive operation is performed.
- If the reference count obtained from the hash table is greater than zero, subtract one from the count and store
-
- judge
isa
Are you in the middle ofdeallocating
If not, throw an exception.
- judge
-
- Send to the object
SEL_dealloc
Information, executiondealloc
Operation.
- Send to the object
Hash table reference count table release reference count
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
SideTable& table = SideTables()[this];
bool do_dealloc = false;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) {
do_dealloc = true;
table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
} else if (it->second < SIDE_TABLE_DEALLOCATING) {
do_dealloc = true;
it->second |= SIDE_TABLE_DEALLOCATING;
} else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
it->second -= SIDE_TABLE_RC_ONE;
}
table.unlock();
if (do_dealloc && performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return do_dealloc;
}
Copy the code
dealloc
The dealloc method is used when a page or object is destroyed.
- (void)dealloc { _objc_rootDealloc(self); } void _objc_rootDealloc(id obj) { assert(obj); obj->rootDealloc(); } inline void objc_object::rootDealloc() {if (isTaggedPointer()) return; // fixme necessary? // no weak reference table, no associated object, no c++ destructor, no hash table reference count 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(); Object_cxxDestruct (obj); // If (CXX) object_cxxDestruct(obj); If (assoc) _object_remove_assocations(obj); obj->clearDeallocating(); } return obj; } inline void objc_object::clearDeallocating() { if (slowpath(! Isa.nonpointer)) {// Non-pointer isa sidetable_clearDeallocating(); } else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) { // non-pointer isa clearDeallocating_slow(); } assert(! sidetable_present()); } void objc_object::sidetable_clearDeallocating() { SideTable& table = SideTables()[this]; table.lock(); RefcountMap::iterator it = table.refcnts.find(this); if (it ! = table.refcnt.end ()) {if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {// Clear the weak reference table weak_clear_no_lock(&table.weak_table, (id)this); Tab.refcnt.erase (it);} // Remove the reference count table from the hash table. } table.unlock(); } void objc_object::clearDeallocating_slow() { assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc)); SideTable& table = SideTables()[this]; table.lock(); Weakly_referenced) {weak_clear_no_lock(&table. Weak_table, (id)this); // Clear weakly_referenced if (ISa.weakly_referenced) {weak_clear_no_lock(&table. Weak_table, (id)this); } if (isa.has_sidetable_rc) {table.refcnt.erase (this); } table.unlock(); }Copy the code
To summarize, the process of calling the dealloc method is as follows:
-
- If it is
TaggedPointer
, the reference count is not processed and returns
- If it is
-
- If it is
nonpointer_isa
, and there is no weak reference table, no associated object, no c++ destructor, and no hash table reference countobject_dispose()
- If it is
-
- call
objc_destructInstance(obj)
And then in the callfree(obj)
- call
-
objc_destructInstance()
Method, determine if there iscxx
The destructor needs to call the destructor method and delete the associated object if it exists.
-
- Clear information about weak reference tables and reference count tables from hash tables.
The release of weak reference tables is described in the weak principle, which is not described here.