NSObject. Mm source
– # # # # # # id
typedef struct objc_object *id;
struct objc_object {
isa_t _Nonnull isa OBJC_ISA_AVAILABILITY;
};
Copy the code
###### ISA_T structure in arm64 architecture (the bits format is the same, some bits are different)
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULLstruct { uintptr_t nonpointer : 1; // uintptr_t has_assoc: 1; Uintptr_t has_cxx_dtor: 1; Uintptr_t shiftcls: 33; // // Uintptr_t magic: 6; // Fixed value 0xd2, used to tell if an object is not initialized during debugging. uintptr_t weakly_referenced : 1; // Whether the object has an weak object, if not, the uintptr_t deallocating: 1 is faster; Uintptr_t has_sidetable_rc: 1; Uintptr_t extra_rc: 19; // Store the result of subtracting the reference count by one# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
};
Copy the code
##### Reference count
Nonpointer (bits. Extra_rc + SideTable) manages reference counts
Nonpointer: On 64-bit systems, in order to reduce memory usage and improve performance, a portion of the ISA field is used to store other information.
######RetainCount source code, directly reflects the three cases
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return(uintptr_t)this; // 1, TaggedPointer sidetable_lock(); isa_t bits = LoadExclusive(&isa.bits); ClearExclusive(&isa.bits);if// 3, Nonpointer -- (bits. extra_rc + SideTable) uintptr_t rc = 1 + bits.extra_rc;if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
returnsidetable_retainCount(); // 2, SideTable hash table}Copy the code
SideTable hash table
Memory management main structure code
struct SideTable { spinlock_t slock; RefcountMap refcnts; // Reference-countedhashTable weak_table_t weak_table; // weak references globallyhashTable};Copy the code
RetainCount is stored in an unsigned integer
uintptr_t
objc_object::sidetable_retainCount()
{
SideTable& table = SideTables()[this];
size_t refcnt_result = 1;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if(it ! Refcnt_result += it->second > SIDE_TABLE_RC_SHIFT; } table.unlock();return refcnt_result;
}
Copy the code
# # # # retain the source code
id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISAassert(! isa.nonpointer);#endifSideTable& table = SideTables()[this]; table.lock(); size_t& refcntStorage = table.refcnts[this]; // The reference count is too large to be managedif(! (refcntStorage & SIDE_TABLE_RC_PINNED)) {// add 1 to the reference count, SIDE_TABLE_RC_ONE = 1<<2) refcntStorage += SIDE_TABLE_RC_ONE; } table.unlock();return (id)this;
}
Copy the code
# # # # release the source code
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISAassert(! isa.nonpointer);#endif
SideTable& table = SideTables()[this];
bool do_dealloc = false;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if(it == table.refcnt.end ()) {// (1) releasing do_dealloc =true; Table. Refcnts [this] = SIDE_TABLE_DEALLOCATING; }else if(it->second < SIDE_TABLE_DEALLOCATING) {// (2) Reference count is 0 do_dealloc =true; The second / / tag dealloc (pictured above) it - > second | = SIDE_TABLE_DEALLOCATING; }else if(! (it->second & SIDE_TABLE_RC_PINNED) {// (3) normal reference count minus 1, (figure above shows, offset by 2 digits, SIDE_TABLE_RC_ONE = 1<<2) it->second -= SIDE_TABLE_RC_ONE; } table.unlock();if(do_dealloc &&performDealloc) {// (4) do_dealloc is true, Void (*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); }return do_dealloc;
}
Copy the code
Look at the next few judgments. If the object is last in the reference count table, do_dealloc is set to true, and the reference count value is SIDE_TABLE_DEALLOCATING (binary 00000010).
If the reference count is smaller than SIDE_TABLE_DEALLOCATING, it is 0.
SIDE_TABLE_RC_ONE = side_table_one; SIDE_TABLE_RC_ONE = side_table_one; Is 1.
4. Finally, if do_dealloc and performDealloc (already true when passed in) are both true, execute SEL_dealloc to release the object. Method returns do_dealloc.
5. Call the parent class Dealloc up to the root class NSobject
Extra_rc + SideTable) ####retain source code
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) {// 2, SideTable hash method ClearExclusive(& ISa.bits);if(! tryRetain && sideTableLocked) sidetable_unlock();if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
if(slowpath(tryRetain && newisa.deallocating) {// Releasing ClearExclusive(& ISa.bits);if(! tryRetain && sideTableLocked) sidetable_unlock();returnnil; } uintptr_t carry; newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); Extra_rc ++ // If newisa.extra_rc++ overflows, carry==1ifSlowpath (carry) {// overflowif(! handleOverflow) { ClearExclusive(&isa.bits); // Empty operation (system reserved)returnrootRetain_overflow(tryRetain); // Call rootRetain(tryRetain,YES)} // call rootRetain_overflow here, just save half of the value of extra_rc into SideTableif(! tryRetain && ! sideTableLocked) sidetable_lock(); sideTableLocked =true;
transcribeToSideTable = true; newisa.extra_rc = RC_HALF; // Overflow, set to half, save half to SideTable newisa.has_sidetable_rc =true; // StoreExclusive saves newISa. bits to ISA. bits, returns YES on successwhileJudge once is over}while(slowpath(! StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));if(slowpath(transcribeToSideTable)) {// Save: save RC_HALF (half) into SideTable sidetable_addExtraRC_nolock(RC_HALF); }if(slowpath(! tryRetain && sideTableLocked)) sidetable_unlock();return (id)this;
}
Copy the code
The extra_rc++ method is simple: 1. 2. The extra_rc is fully loaded with half of the stock in SideTable (arm64’s EXTRA_RC is 19 bits, fully stocked)
###### debit saving method
bool
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
assert(isa.nonpointer);
SideTable& table = SideTables()[this];
size_t& refcntStorage = table.refcnts[this];
size_t oldRefcnt = refcntStorage;
// isa-side bits should not be sethere assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0); assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0); // System count limit, directtrue
if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;
uintptr_t carry;
size_t newRefcnt =
addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
if(carry) {// If the borrow is stored here and overflows, As SIDE_TABLE_RC_PINNED number (32 or 64 largest numerical digits - 1) refcntStorage = SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);return true;
}
else {
refcntStorage = newRefcnt;
return false; }}Copy the code
# # # # # release the source code
ALWAYS_INLINE bool
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)) {// This is ClearExclusive(&isa.bits);if (sideTableLocked) sidetable_unlock();
returnsidetable_release(performDealloc); } uintptr_t carry; newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // if extra_rc==0, extra_rc-- is negative, carry=1if(slowpath(carry)) { goto underflow; }}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)) {// With a borrow save, rootRelease_underflow re-enters the functionif(! handleUnderflow) { ClearExclusive(&isa.bits);returnrootRelease_underflow(performDealloc); } // Some lock operationsif(! sideTableLocked) { ClearExclusive(&isa.bits); sidetable_lock(); sideTableLocked =true; goto retry; } // Retrieve the number of borrowed references (maximum number of borrowed references RC_HALF) size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);if(borrowed > 0) { // extra_rc-- newisa.extra_rc = borrowed - 1; If &isa.bits and oldisa.bits are equal, then assign the value of newISa. bits to &ISa. bits and returntrue
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
if(! Stored) {// Failed to save, try again (duplicate code) isa_t oldisA2 = LoadExclusive(&isa.bits); isa_t newisa2 = oldisa2;if (newisa2.nonpointer) {
uintptr_t overflow;
newisa2.bits =
addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
if(! overflow) { stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, newisa2.bits); }}}if(! Stored) {// Failed, put the number of times back into SideTable, retry sidetable_addExtraRC_nolock(borrowed); goto retry; } // release end sidetable_unlock();return false;
}
else{// If sideTable also has no count, then we go to the dealloc stage below}} // If there is no borrow save count, we go hereif(slowpath(newisa.deallocating)) {// If the object is already being released, error warning: release ClearExclusive(& ISa.bits) multiple times;if (sideTableLocked) sidetable_unlock();
return overrelease_error();
}
newisa.deallocating = true; / / save bitsif(! StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;if (slowpath(sideTableLocked)) sidetable_unlock();
__sync_synchronize();
if(performDealloc) {call dealloc ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); }return true;
}
Copy the code
# # # # NSobject dealloc source code
void *objc_destructInstance(id obj)
{
if (obj) {
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
Copy the code
Object_cxxDestruct does three simple things: 1. Execute object_cxxDestruct and process the member variables of this class (the parent is handled by the parent). Object_cxxDestruct contains the final for (; cls; CLS = CLS ->superclass){execute. Cxx_destruct}; Cxx_destruct finally executes to process the member variable: Strong objc_storeStrong(&ivar, nil)release object, ivar assign nil, ② weak objc_destroyWeak(&ivar) destroy the ivAR address in the weak table.
2. Execute _object_remove_assocations to remove the object from the assocate category.
3. Perform objc_clear_deallocating, clearing the reference-count table and the weak reference table, locating all weak references to nil (which is where the weak variable can be safely empty).
#### A simple summary of the object release process -dealloc
Extra_rc triggers dealloc by subtracting 0, * marking the object Isa.deallocating =true* There can be no new __weak references * calls to [self dealloc] (MRC needs to manually release strongly referenced variables in the dealloc method) * Calls to -dealloc for each level of the parent class in the inheritance relationship. All the way to the root class (usually NSObject) 2. The NSObject call -dealloc * does just one thing: call object_Dispose () in the Objective-C Runtime 3. Call object_dispose() * objc_destructInstance(obj); * free(obj); 4. Objc_destructInstance (obj) performs three operations *if(cxx) object_cxxDestruct(obj); // Release (1) strong objc_storeStrong(&ivar, nil)release object, ivar assigned nil, (2) weak ivar, out of scope, Objc_destroyWeak (&ivar) >> storeWeak(&ivar, nil) removes the address of ivar pointing to nil from the object's weak table. *if(assoc) _object_remove_assocations(obj); // Remove Associate data (that's why you don't need to manually remove it) * obj->clearDeallocating(); // Empty the reference count table, empty the weak variable table and point all references to nilCopy the code
Objc_storeStrong source
Reassign to a strong pointer, for example
NSObject *obj = [NSObject new]; NSObject *obj2 = [NSObject new]; NSObject *strongObj = obj; strongObj = obj2; // execute objc_storeStrong(&strongObj, obj2); { NSObject *obj = [NSObject new]; } // out of scope, do objc_storeStrong(&obj, nil);Copy the code
void
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
Copy the code
Retain the new object and repoint the strong pointer to the new object. Release the old object.
Reference: iOS Advanced — iOS (Objective-C) memory management · Dealloc process under 2 ARC and the exploration of. Cxx_destruct