Summary of basic principles of iOS

This article mainly analyzes the memory management scheme in memory management, and the underlying source code analysis of Retain, retainCount, Release, dealloc

ARC & MRC

Memory management schemes in iOS can be roughly divided into two categories: MRC (manual memory management) and ARC (automatic memory management).

MRC

  • In the MRC era, the system determines whether an object is destroyed by its reference count, with the following rules

    • The object isWhen creatingThe reference counts are1
    • When objectReferenced by another pointer, you need to manually invoke the[objc retain], make the objectReference count +1
    • When pointer variables are no longer using objects, they need to be called manually[objc release]toThe release ofObject that makes the objectReference count -1
    • When an objectThe reference count is 0, the system willThe destructionThis object
  • So, in MRC mode, you have to follow: who creates, who releases, who references, who manages ARC

  • ARC mode is an automatic management mechanism introduced in WWDC2011 and iOS5, that is, automatic reference counting. Is a feature of the compiler. The rules are the same as MRC, except that ARC mode does not require manual retain, release, and autorelease. The compiler inserts release and autorelease in place.

Memory layout

We introduced the five regions of memory in iOS- Underlying Principles 24: The Five Regions of memory. In fact, in addition to the memory area, there are also kernel area and reserved area. Take the 4GB mobile phone as an example, as shown below, the system allocates 3GB to the five major areas + reserved area, and the remaining 1GB to the kernel area

  • The kernel area: Area of the system used for kernel processing operations
  • Five regions: no further explanation here, please refer to the above link for details
  • The reservations: Reserved for the system to handle nil, etc

There is a question as to why the last memory address of the five extents starts at 0x00400000. The main reason for this is that 0x00000000 means nil, and you can’t directly represent a segment as nil, so you’re given a separate chunk of memory for things like nil

Memory layout

Interview question 1: Is there a difference in memory between global and local variables? If so, what is the difference?

  • Is there a difference
  • The global variableSaved in memoryGlobal storage (i.e. BSS +data segment)Occupies static storage units
  • A local variableStored in theThe stackIn, only inThe storage unit for variables is allocated dynamically only when the function is called

Question 2: Can you modify global variables, global static variables, local static variables, local variables in a Block?

  • You can modify global variables, global static variables, because global variables and static global variables are global and have a wide scope

  • Local static variables can be modified, not local bin

    • Local static variables (static modified) and local variables, captured by the block from the outside, becomes__main_block_impl_0Member variables of this structure
    • A local variableBased onValue wayIn the constructor of a block, only variables that will be used in the block are captured. Since only values of variables are captured, not memory addresses, they are captured inside the blockCan't changeValue of a local variable
    • Local static variableBased onPointer to the form, is captured by a block, because it is capturing a pointer, soYou can modifyValue of a local static variable
  • In ARC, once the __block modifier is used and changed in a block, copy is triggered, and the block is copied from the stack to the heap, which is the heap block

  • In ARC mode, blocks referencing id types are retained with or without __block modifier. For underlying data types, variable values cannot be modified without __block. If there is a __block modifier, the __Block_byref_a_0 structure is also modified at the bottom, and its internal forwarding pointer points to the address after copy to achieve the value modification

Memory management scheme

In addition to the MRC and ARC mentioned above, there are three memory management schemes

  • Tagged Pointer: is used to deal with small objects, such as NSNumber, NSDate, and small NSString
  • Nonpointer_isa: non-pointer type ISA, mainly used to optimize 64 bit addresses, this inIOS – Underlying Principles 07: How ISA is associated with classesIn this article, it has been introduced
  • SideTables:Hash table, there are two main tables in the hash table, respectivelyReference counter table,A weak reference table

Here we will focus on Tagged Pointer and SideTables. We will introduce Tagged Pointer with an interview question

The interview questions

What’s wrong with the following code?

/ / * * * * * * * * * code 1 * * * * * * * * * - (void) taggedPointerDemo {self. The queue = dispatch_queue_create (" com. CJL. Cn." DISPATCH_QUEUE_CONCURRENT); for (int i = 0; i<10000; i++) { dispatch_async(self.queue, ^{ self.nameStr = [NSString stringWithFormat:@"CJL"]; // Alloc heap iOS optimization-taggedPointer NSLog(@"%@", self.namestr); }); }} / / * * * * * * * * * code 2 * * * * * * * * * - (void) touchesBegan: (NSSet < > UITouch * *) touches withEvent: UIEvent *) event {NSLog (@ "to"); for (int i = 0; i<10000; I++) {dispatch_async(self.queue, ^{self.namestr = [NSString stringWithFormat:@"CJL_ harder, luckier!! "] ; NSLog(@"%@",self.nameStr); }); }}Copy the code

Running the code above, you can see that taggedPointerDemo runs fine on its own, once the touchesBegan method is triggered. The program crashes because multiple threads are releasing an object at the same time, causing excessive releasing to crash. The root cause is the underlying nameStr type inconsistency, which we can see through debugging

  • taggedPointerDemoIn the methodnameStrType isNSTaggedPointerStringAnd stored in theThe constant area. becausenameStrinallocDistribution in theThe heap areaBecause it is small, it is optimized by iOS in XcodeNSTaggedPointerStringType stored in the constant area
  • touchesBeganIn the methodnameStrType isNSCFStringType stored inThe heap

Memory management for NSString

We can test the memory management of AN NSString in two ways, with NSString initialization

  • throughWithString + @""Mode initialization
  • throughWithFormatMode initialization
#define KLog(_c) NSLog(@"%@ -- %p -- %@",_c,_c,[_c class]); - (void)testNSString{// Initialization method 1: WithString + @"" NSString *s1 = @"1"; NSString *s2 = [[NSString alloc] initWithString:@"222"]; NSString *s3 = [NSString stringWithString:@"33"]; KLog(s1); KLog(s2); KLog(s3); NSString *s4 = [NSString stringWithFormat:@"123456789"]; NSString stringWithFormat:@"123456789"]; NSString *s5 = [[NSString alloc] initWithFormat:@"123456789"]; NSString *s6 = [NSString stringWithFormat:@"1234567890"]; NSString *s7 = [[NSString alloc] initWithFormat:@"1234567890"]; KLog(s4); KLog(s5); KLog(s6); KLog(s7); }Copy the code

Here are the results of the run

Therefore, it can be concluded from the above that there are three main types of memory management for NSString

  • __NSCFConstantString: string constant, a compile-time constant with a large retainCount value that does not cause a reference count change, stored in the string constants section

  • __NSCFString: is an NSString subclass created at run time. The reference count is incremented by one and stored on the heap

  • NSTaggedPointerString: Label pointer, which is apple’s optimization of NSString, NSNumber and other objects in 64-bit environment. For NSString objects

    • whenThe string is a combination of digits and letters and contains 9 characters or less, automatically becomesNSTaggedPointerStringType stored inThe constant area
    • When you haveChinese or other special symbols, will directly become__NSCFStringType stored inThe heap area

Tagged Pointer Indicates a small object

In order to explore the handling of Tagged Pointer, we need to enter objC source code to check the handling of Tagged Pointer in the retain and release source code

Small object reference counting processing analysis

  • To viewsetProperty -> reallySetPropertySource code, which is onNew value retain, old value release

Enter objc_retain and objc_release source code, here check whether it is a small object, if it is a small object, will not be retained or released, will return directly. Therefore, one conclusion can be drawn: if the object is small, no retain and release is done

//****************objc_retain**************** __attribute__((aligned(16), flatten, noinline)) id objc_retain(id obj) { if (! obj) return obj; If (obj->isTaggedPointer()) return obj; // If it is not a small object, retain return obj->retain(); } //****************objc_release**************** __attribute__((aligned(16), flatten, noinline)) void objc_release(id obj) { if (! obj) return; // If (obj->isTaggedPointer()) return; Release return obj->release(); }Copy the code

Address analysis of small objects

So let’s continue with NSString, for NSString

  • The generalNSStringObject Pointers, bothString value + pointer addressThe two are separate
  • forTagged PointerA pointer, thePointer + valueCan be reflected in small objects. soTagged PointerContains both Pointers and values

In the previous article about class loading, _read_images source there is a way of treatment for small objects, namely initializeTaggedPointerObfuscator method

  • Enter the_read_images -> initializeTaggedPointerObfuscatorThe source code to achieve
static void initializeTaggedPointerObfuscator(void) { if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) || // Set the obfuscator to zero for apps linked against older SDKs, // in case they're relying on the tagged pointer representation. DisableTaggedPointerObfuscation) { objc_debug_taggedpointer_obfuscator = 0; } // after iOS14, there was confusion about small objects, Else {// Pull random data into the variable by confusing it with the +_OBJC_TAG_MASK operation then shift away all non-payload bits. arc4random_buf(&objc_debug_taggedpointer_obfuscator, sizeof(objc_debug_taggedpointer_obfuscator)); objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK; }}Copy the code

In the implementation, we can see that after iOS14, Tagged Pointer uses obfuscation, as shown below

  • We can pass it in the source codeobjc_debug_taggedpointer_obfuscatorSearch for taggedPointercodinganddecoding“To see how the underlying obfuscation is handled
//įž–į 
static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
    return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}
//įž–į 
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
Copy the code

Through the implementation, we can know that in the encoding and decoding part, there are two layers of xOR, its purpose is to get the small object itself, for example, take 1010 0001 as an example, assume that the mask is 0101 1000

1010 0001 ^0101 1000 mask (encoding) 1111 1001 ^0101 1000 mask (decoding) 1010 0001Copy the code

So outside, to get the real address of the small object, we can copy the decoded source code outside and decode the NSString obfuscated part, as shown below

Observing the address of the decoded small object, 62 represents the ASCII code of B, and taking NSNumber as an example, it can also be seen that 1 is our actual value

Here, we verify that the small object pointer addresses do store values. What is the meaning of 0xA and 0xb in the small object address high?

//NSString
0xa000000000000621

//NSNumber
0xb000000000000012
0xb000000000000025
Copy the code
  • Need to go to the source code to view_objc_isTaggedPointerSource code, mainly throughRetain the value of the highest bit(that is, a 64-bit value) to determine whether it equals_OBJC_TAG_MASKTwo to the sixty-threeIs it a small object
Static inline bool _objc_isTaggedPointer(const void * _Nullable PTR) {const void * _Nullable PTR = 1; Return ((uintptr_t) ptr&_objc_tag_mask) == _OBJC_TAG_MASK; }Copy the code

Therefore, 0xA and 0xB are mainly used to determine whether they are taggedpointer small objects, that is, to determine whether the 64th bit is 1 (the address of the pointer is both the address of the pointer and the value).

  • 0xaConvert to binary1, 010(64 is 1. The last three digits from 63 to 61 indicate tagType -2.NSStringtype
  • 0xbConvert to binary1, 011(64 is 1. The last three digits from 63 to 61 indicate tagType -3.NSNumberType, one thing to notice here is ifNSNumberThe value is- 1, its addressThe values are in complementThe said

You can enter the enumeration of the _objc_makeTaggedPointer method using its parameter tag type objc_tag_index_t, where 2 represents NSString and 3 represents NSNumber

Similarly, we can define an NSDate object to verify that its tagType is 6. By printing the result, its address high is 0xE, translated to binary as 1 110, excluding the 64-bit 1, and the remaining 3 bits translated to decimal as exactly 6, which matches the enumerated values above

Tagged Pointer summary

  • Tagged PointerSmall object type (used for storageNSNumber, NSDate, small NSString), small object Pointers are no longer simple addresses, butAddress + value, i.e.,The real valueSo, actuallyIt's not an object anymore, it's just a normal variable in an object's skinAnd to. So you can read it directly. Advantage isSmall footprint and memory saving
  • Tagged PointerSmall objectsRetain and release will not be entered, butIt goes straight backThat means thatNo ARC management is required, soCan be directly released and recovered by the system autonomously
  • Tagged PointertheMemory is not stored in the heapIn, but inThe constant areaBut alsoMalloc and free are not required, so it can be read directly, compared to data stored in the heapIt's about three times faster in terms of efficiency.createEfficiency compared to the heap areaIt's about 100 times faster
  • So, all in all,taggedPointerMemory management is much faster than conventional memory management
  • Tagged Pointer64-bit address of,The first fourdelegatestype, the last four bits are mainly used for the system to do some processing,The middle 56 bits are used to store values
  • Memory optimization suggestion: YesNSStringSay, when a stringsmaller, you are advised to directly pass@ ""Initialize as stored inThe constant area, can be read directly. thanWithFormat Initialization modeMore quickly

SideTables hash

When the reference count is stored to a certain value, it is not stored in extra_RC of Nonpointer_isa’s bitfield, but in the SideTables hash table

Let’s continue exploring the underlying implementation of the reference count RETAIN

Retain source code Analysis

  • Objc_retain -> retain -> rootRetain source code implementation, mainly has the following parts of the logic:

    • [First step] Determine whether it is Nonpointer_isa

    • [Step 2] Operate the reference count

      • 1. If notNonpointer_isaIs directly operatedSideTablesHash table. Instead of just one hash table, there are many hash tables (more on this later).
      • 2, judgment,Is it being released?If you are releasing, the dealloc process is executed
      • 3, performextra_rc+1That is, the reference count +1 operation and gives a reference countStatus identification CARRYUsed to indicateextra_rcWhether the full
      • 4, ifcarrayState representation ofExtra_rc's reference count is fullNow you need to operateHash table, that is, take out half of the full state and save toextra_rcAnd the other half have hash tablesrc_half. The reason for doing this is that if all stores are stored in the hash table, every operation on the hash table needs to be unlocked, which is time-consuming and consumes high performanceEqually dividedThe purpose of the operation is toTo improve performance
ALWAYS_INLINE id objc_object::rootRetain(bool tryRetain, bool handleOverflow) { if (isTaggedPointer()) return (id)this; bool sideTableLocked = false; bool transcribeToSideTable = false; // Why isa? Because the reference count needs to be +1, i.e. retain+1, and the reference count is stored in the bits of isa, the oldisa needs to be replaced with the oldisa isa_t oldisa. isa_t newisa; // do {transcribeToSideTable = false; oldisa = LoadExclusive(&isa.bits); newisa = oldisa; // check if nonpointer isa if (slowpath(! Newisa.nonpointer)) {// If it is not nonpointer isa, sidetable 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 Overmark if (slowPath (tryRetain && newisa.deallocating) { ClearExclusive(&isa.bits); if (! tryRetain && sideTableLocked) sidetable_unlock(); return nil; } uintptr_t carry; Bits = addC (newISa.bits, RC_ONE, 0, &carry); Extra_rc ++ // To judge if extra_rc is full, carry is the identifier if (slowPath (carry)) {// newisa.extra_rc++ overstep 3 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; 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. // The other half is stored in the hash table rc_half (full is 8 bits, half is 1 to the left by 7 bits, divided by 2) // The purpose of this operation is to improve performance, because if both are stored in the hash table, when release-1 needs to be accessed, the hash table needs to be unlocked every time, which costs performance. With half of extra_rc stored, you can manipulate EXTRA_RC directly without manipulating hash tables. Sidetable_addExtraRC_nolock (RC_HALF); } if (slowpath(! tryRetain && sideTableLocked)) sidetable_unlock(); return (id)this; }Copy the code

Question 1: Why do hash tables contain more than one hash table? What is the maximum number?

  • If the hash table only has one table, meaning that all global objects are stored in a table and unlocked (lock is to lock the read and write of the entire table). When unlocking, since all data is in a table, it meansData insecurity
  • ifEach object opens a tableWill,Cost performanceSo you can’t have an infinite number of tables
  • The hash table type isSideTable, is defined as follows
struct SideTable { spinlock_t slock; // open/unlock RefcountMap refcnts; Weak_table_t weak_table; // Weak reference table.... }Copy the code
  • By looking at thesidetable_unlockMethods to locateSideTablesAnd its interior is throughSideTablesMapGet from the get method. whileSideTablesMapIs through theStripedMap<SideTable>The definition of the
Void objc_object:: sideTable_unlock () {//SideTables hash table = SideTables()[this]; //SideTables hash table = SideTables()[this]; table.unlock(); } 👇 static StripedMap<SideTable>& SideTables() {return sidetablesmap.get (); } 👇 static objc::ExplicitInit<StripedMap<SideTable>> SideTablesMap;Copy the code

Thus enter the definition of StripedMap. It can be seen from this that there can only be 8 hash tables in the real machine at the most at the same time

Q2: Why hash tables instead of arrays and linked lists?

  • An array of: The characteristics lie inEasy to query (that is, access by subscript), more trouble to add and delete(Similar to what we talked about beforemethodListThrough theMemcopy, memmoveAdd and delete, very troublesome), so the characteristics of data areFast reading and inconvenient storage
  • The list: The characteristics lie inEasy to add and delete, slow query (need to start from the beginning node traversal query), so the properties of linked lists areFast storage, slow reading
  • Hash tablethenatureIt is aHash tableThe hash tableBrings together the best of arrays and linked lists.Add, delete, change and check are more convenient, e.g.Zip hash tableIn the previous lock article, we talked about thistlsThe storage structure ofZip formIs the most commonly used, as shown below

From SideTables -> StripedMap -> indexForPointer we can verify that the hash index is calculated by the hash function and that SideTables can use []

So, to sum up, the underlying flow of Retain is as follows

Retain a complete answer

  • retainIt’s going to be at the bottom firstCheck whether it is Nonpointer ISAIf theIf no, the hash table is directly manipulated to perform the +1 operation
  • ifIs Nonpointer isa, you also need toDetermine if the release is in progress, if you areRelease, the dealloc process is executed, free the weak reference table and reference technical table, and finally free the object memory
  • ifNonpointer ISA does the normal reference count +1 if it is not being released.One thing to notice here is,extra_rcOnly on a real machine8-bit value used to store reference countsWhen the storageFull of, you need toWith hash tableUsed for storage. The need will be fullextra_rcSplit in half, half (i.e. 2^7) stored inHash tableIn the. The other half is still stored inextra_rc+1 or -1 for regular reference counting, and then return

Release source code analysis

After analyzing the underlying implementation of Retain, let’s look at the underlying implementation of Release

  • SetProperty -> reallySetProperty -> objc_release -> release -> rootRelease -> rootRelease The operation is the reverse of retain

    • Check whether it is Nonpointer ISA. If not, perform -1 on the hash table

    • In the case of Nonpointer ISA, perform -1 on the reference count in EXTRA_RC and store the extra_RC state at that time into CARRY

    • If the state carray is 0, the underflow process is entered

    • The underflow process has the following steps:

      • judgeHash tableIn theWhether half of the reference count is stored
      • If yes, run theHash tableIn theTake out theStore half of the reference count to proceed1 the operationAnd store it toextra_rcIn the
      • If at this timeextra_rcIf there is no value and the hash table is empty, the destructor is directly performed, i.edeallocThe operation is automatically triggered
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; // check if Nonpointer isa if (slowpath(! Newisa.nonpointer)) {// If not, operate hash table -1 ClearExclusive(& ISa.bits); if (rawISA()->isMetaClass()) return false; if (sideTableLocked) sidetable_unlock(); return sidetable_release(performDealloc); } // don't check newisa.fast_rr; we already called any RR overrides uintptr_t carry; Bits extra_RC-1 newISa.bits = subc(newisa.bits, RC_ONE, 0, &carry); Slowpath (carry)) {// Don't ClearExclusive() goto underflow; } } while (slowpath(! StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits))); if (slowpath(sideTableLocked)) sidetable_unlock(); return false; underflow: // newisa.extra_rc-- underflowed: borrow from side table or deallocate // abandon newisa to undo the decrement newisa = oldisa; Slowpath (newisa.has_sidetable_rc)) {if (! handleUnderflow) { ClearExclusive(&isa.bits); return rootRelease_underflow(performDealloc); } // Transfer retain count from side table to inline storage. if (! sideTableLocked) { ClearExclusive(&isa.bits); sidetable_lock(); sideTableLocked = true; // Need to start over to avoid a race against // the nonpointer -> raw pointer transition. goto retry; } // Try to remove some retain counts from the side table sidetable_subExtraRC_nolock(RC_HALF); // To avoid races, has_sidetable_rc must remain set // even if the side table count is now zero. if (borrowed > 0) { // Side table retain Count decreased. // Try to add them to the inline count. // Newisa. extra_rc = external-1; // redo the original decrement too bool stored = StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits); if (! stored) { // Inline update failed. // Try it again right now. This prevents livelock on LL/SC // architectures where the  side table access itself may have // dropped the reservation. 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) { // Inline update failed. // Put the retains back in the side table. sidetable_addExtraRC_nolock(borrowed); goto retry; } // Decrement successful after borrowing from side table. // This decrement cannot be the deallocating decrement - the side // table lock and has_sidetable_rc bit ensure that if everyone // else tried to -release while we worked, the last one would block. sidetable_unlock(); return false; } else {// Side table is empty after all. fall-through to the dealloc path.} If (slowPath (newisa.dealLocating)) {ClearExclusive(& Isa.bits); if (sideTableLocked) sidetable_unlock(); return overrelease_error(); // does not actually return } newisa.deallocating = true; if (! StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; if (slowpath(sideTableLocked)) sidetable_unlock(); __c11_atomic_thread_fence(__ATOMIC_ACQUIRE); If (performDealloc) {// Send a dealloc message ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc)); } return true; }Copy the code

Therefore, to sum up, the underlying process of Release is shown in the figure below

Dealloc source code analysis

The dealloc destructor is mentioned in both retain and release’s underlying implementations. Let’s look at the underlying implementation of dealloc

  • Dealloc -> _objc_rootDealloc -> rootDealloc source code implementation, there are two main things:

    • According to the conditionCheck whether there is ISA, CXX, associated object, weak reference table, reference count table, if not, thenDirect free releases memory
    • If yes, enterobject_disposemethods
Inline void objc_object::rootDealloc() {// What do I need to do to release an object? Isa-cxx - Associated Objects - Weak References table - Reference Count table //2, free if (isTaggedPointer()) return; // fixme necessary? // Free if (fastPath (isa.nonpointer &&! isa.weakly_referenced && ! isa.has_assoc && ! isa.has_cxx_dtor && ! isa.has_sidetable_rc)) { assert(! sidetable_present()); free(this); Dispose (id)this); // Dispose (id)this); }}Copy the code
  • Enter the object_Dispose source code for the following purposes

    • To destroy an instance, perform the following operations

      • Call the c++ destructor
      • Deleting an Associated Reference
      • Release hash
      • Clear the weak reference table
    • Free Free memory

id object_dispose(id obj) { if (! obj) return nil; // Destroy the instance without freeing the memory objc_destructInstance(obj); // Free memory (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); // Call C ++ destruct if (CXX) object_cxxDestruct(obj); // Delete associated references if (assoc) _object_remove_assocations(obj); / / release the obj - > clearDeallocating (); } return obj; } 👇 inline void objc_object: : clearDeallocating () {/ / determine whether for nonpointer isa if (slowpath (! Isa.nonpointer)) {// Slow path for raw pointer isa. // If not, release the hash table sidetable_clearDeallocating(); } // If yes, Empty a weak reference table + hash else if (slowpath (isa. Weakly_referenced | | isa. Has_sidetable_rc)) {/ / missile path for non - pointer isa with weak refs and/or side table data. clearDeallocating_slow(); } assert(! sidetable_present()); } 👇 NEVER_INLINE void objc_object: : clearDeallocating_slow () {ASSERT (isa) nonpointer && (isa) weakly_referenced | | isa.has_sidetable_rc)); SideTable& table = SideTables()[this]; table.lock(); If (ISa.weakly_referenced) {// Clear weak reference table weak_clear_no_lock(&table. Weak_table, (id)this); } if (isa.has_sidetable_rc) {table.refcnt.erase (this); } table.unlock(); }Copy the code

So, to sum up, the underlying flow chart of Dealloc is shown below

So, so far, from the initial alloc low-level analysis (see iOS- Underlying Principles 02: Alloc & Init & New source analysis) -> retain -> release -> dealloc is all connected

RetainCount source code analysis

The reference count analysis is illustrated by an interview question

Interview question: What is the reference count of the object created by alloc?

  • Define the following code to print its reference count
NSObject *objc = [NSObject alloc];
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)objc));
Copy the code

The print result is as follows

  • Enter theretainCount -> _objc_rootRetainCount -> rootRetainCountSource code, its implementation as follows
- (NSUInteger)retainCount { return _objc_rootRetainCount(self); } 👇 uintptr_t _objc_rootRetainCount(id obj) {ASSERT(obj); return obj->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) {// Alloc creates objects with a reference count of 0, including sideTable, so for alloc, 0+1=1, This is why the reference count obtained via retainCount is 1 uintptr_t rc = 1 + bits.extra_rc; if (bits.has_sidetable_rc) { rc += sidetable_getExtraRC_nolock(); } sidetable_unlock(); return rc; } // If not, return sidetable_unlock() as normal; return sidetable_retainCount(); }Copy the code

Here we can use source breakpoint debugging to view the value of extra_rc, as shown below

The object created by alloc actually has a reference count of 0, and a reference count of 1 is printed because the underlying rootRetainCount method has a reference count of +1 by default. To prevent objects created by alloc from being freed (a reference count of 0 would be freed), the underlying program defaults to +1 at compile time. In fact, the reference count in EXTRA_RC is still 0

conclusion

  • allocObject createdThere is no retain and release
  • allocObject creatingThe reference count is 0, can be inCompile time, the programThe default add 1Find the new version of the source code, inline Uintptr_t

The objc_object::rootRetainCount() method is no longer implemented with extra_rc plus one. The extra_rc is assigned 1 in initIsa.)