preface

Memory management is a very important part of ios development, and the right time to free and reclaim memory can ensure efficient application. Stack memory, heap memory, we really need to care about the heap memory, stack memory is managed by the system itself. Functions and variables are stored in the stack, and their memory management is done by the system. Objects created by alloc, block copy and other memory are stored in the heap, and their memory management needs to be implemented by ourselves. Objects are released when the reference count is 0, so we need to pay attention to how the reference count of objects is increased. We often use the weak modifier object to solve circular references. Why does the weak modifier object solve circular references? So let’s explore with these questions.

TaggedPointer

Before exploring reference counting, let’s take a look at TaggedPointer, because the related source code exploration below first determines whether an object is of type TaggedPointer.

TaggedPointer origin?

Since apple introduced the iphone5s in 2013, the addressing space on iOS has expanded to 64 bits. We can use 63 bits to represent a number (one sign bit). So this number has a range of 2 to the 63, and obviously we don’t use numbers that big, so when we define a number NSNumber *num = @100, we’re actually wasting a lot of memory. Apple recognized the problem and introduced tagged Pointer, a special “pointer” that stores real data and additional information, not an address. This is very similar to our previous exploration of ISA. When ISA is not a pure pointer, it stores not only the associated object but also the reference count of the object. Apple has optimized ISA to store more information.

TaggedPointer characteristics

  • Tagged Pointer is used for storageSmall objects, e.g.NSNumberandNSDate
  • Tagged Pointer Specifies the value of a PointerIt's not an address anymore, butThe real value. So, it’s not really an object anymore, it’s just a normal variable in an object’s skin. So, its memory is notNot stored in the heapIn, there is no need for malloc and free
  • Three times more efficient at memory reads and 106 times faster at creation.
  • The length of the string is10When within, useinitwithformatInitialized string of typeNSTaggedPointerStringType; whenMore than tenThe type of the string is__NSCFString

TaggedPointer judgment

static inline bool 
_objc_isTaggedPointer(const void * _Nullable ptr)
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

#if OBJC_SPLIT_TAGGED_POINTERS
#   define _OBJC_TAG_MASK (1UL<<63)
#elif OBJC_MSB_TAGGED_POINTERS
#   define _OBJC_TAG_MASK (1UL<<63)
#else
#   define _OBJC_TAG_MASK 1UL
#endif
Copy the code

Determining whether an object is of type NSTaggedPointerString is essentially a bit-by-bit operation of the object’s address with _OBJC_TAG_MASK, which is then compared with _OBJC_TAG_MASK. An object with a 64-bit binary address (_OBJC_TAG_MASK) is of type tagged pointer if the highest bit in the 64-bit data is 1. Here is an example of string initialization

  NSString *str = [NSString stringWithFormat:@"hello"];
    NSLog(@"str:%p-%ld-%@",str,CFGetRetainCount((CFTypeRef)str),str.class);

    NSString *str2 = @"Helloword, quick to learn, not impatient";
    NSLog(@"str2:%p-%ld-%@",str2,CFGetRetainCount((CFTypeRef)str2),str2.class);

   
    NSString *str3 = [[NSString alloc]initWithString:str2];
    NSLog(@"str3:%p-%ld-%@",str3,CFGetRetainCount((CFTypeRef)str3),str3.class);

    NSString *str4 = [NSString stringWithString:str2];
    NSLog(@"str4:%p-%ld-%@",str4,CFGetRetainCount((CFTypeRef)str4),str4.class);

    NSString *str5 = [[NSString alloc]initWithFormat:@"he%@"The @"llo"];
    NSLog(@"str5:%p-%ld-%@",str5,CFGetRetainCount((CFTypeRef)str5),str5.class);

    NSString *str6 = [[NSString alloc]initWithFormat:@"he%@"The @"Llo, quick to learn, slow to learn."];
    NSLog(@"str6:%p-%ld-%@",str6,CFGetRetainCount((CFTypeRef)str6),str6.class);
Copy the code

Output:

str:0xa00006f6c6c65685-9223372036854775807-NSTaggedPointerString
str2:0x1085cf0a0-1152921504606846975-__NSCFConstantString
str3:0x1085cf0a0-1152921504606846975-__NSCFConstantString
str4:0x1085cf0a0-1152921504606846975-__NSCFConstantString
str5:0xa00006f6c6c65685-9223372036854775807-NSTaggedPointerString
str6:0x600001759c40-1-__NSCFString
Copy the code

Initialize a string using stringWithFormat, string direct assignment, initWithString, stringWithString, initWithFormat, and print a pointer, reference count, and string type

  • NSCFConstantStringThe reference count of the string type is 1152921504606846975, and the pointer is 0x1085CF0A0, indicating that NSCFConstantString is oneString constantAfter creation, the object cannot be released, the same content__NSCFConstantStringObjects have the same address, which means that a constant string object is a singleton
  • NSCFStringand__NSCFConstantStringDifferent,_NSCFStringObject is inThe runtimeA kind of creationNSStringSubclass, it is not a string constant, so and otherobjectThe same is obtained when it is created1Reference count of
  • NSTaggedPointerStringSimilar to NSCFConstantString, its reference count is 9223372036854775807, which is also very largeA singleton constant that cannot be freedAnd if the value is the same, the pointer is the same, except that NSTaggedPointerString is a special pointer whose valueStored in theIt’sPointer to the addressIn the.
  • Only usestringWithFormatandinitWithFormatInitializes the string object and the string length is in10 the followingIs theTaggedPointerType, or NSCFString otherwise

Conclusion:

  • taggedpointerOBJC_TAG_NSString, OBJC_TAG_NSNumber, OBJC_TAG_NSIndexPath, etc. TaggedPointer can be identifiedSign aI’m not going to go into the details here.
  • Tagged Pointer follows the previous explorationNature of objectJust like in the article, there’s a property in the objectisaApple has optimized ISA to store not only associated objects but also other values, such as object reference counts and so onThe first flag bitTo determine whether ISA isnonpointerType, 0 means pure pointer, 1 means isa contains class information, objects, reference counts, etc.,For one[0].extra_rc: indicates the objectReferential countFor example, if the object’s reference count is 10, then extra_rc is 9, and extra_rc takeseight[57] 64

Object reference count

So I’m going to write a demo, where multiple threads are reading and writing NSString.

self.queue = dispatch_queue_create("com.cooci.cn", DISPATCH_QUEUE_CONCURRENT);
   // Multithreading example 1
    for (int i = 0; i<1000000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"hello"];
             NSLog(@"% @",self.nameStr);
        });
    }
     // Multithreading example 2
    for (int i = 0; i<10000000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"Hello_ Learn in harmony without being impatient"];
            NSLog(@"% @",self.nameStr);
        });

    }
Copy the code

Analysis: In example 1, nameStr is a TaggedPointer type, while in Example 2, nameStr is a NSCFString type. The NSCFString type is very similar to the ordinary object type. Why is the memory read and write safe in example 1 and not in example 2? First of all, memory reads and writes are the retain and release of the new value, so let’s focus on these two functions.

objc_retain

objc_retain(id obj)
{
    if (obj->isTaggedPointerOrNil()) return obj;
    return obj->retain();
}
Copy the code

Check whether the object is of type TaggedPointer. If it is returned directly, it does not retain the new value or release the old value, so its memory read and write is safe and efficient. If not TaggedPointer then follow the source code and find the rootRetain function.

rootRetain

objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
    if (slowpath(isTaggedPointer())) return (id)this;
    bool sideTableLocked = false;
    bool transcribeToSideTable = false;
    isa_t oldisa;
    isa_t newisa;
    oldisa = LoadExclusive(&isa.bits);
    / /... Omit some interfering code
    do {
        transcribeToSideTable = false;
        newisa = oldisa;
        // If it is pure ISA, the reference count table in sideTable +1,
        if(slowpath(! newisa.nonpointer)) { ClearExclusive(&isa.bits);if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain(sideTableLocked);
        }
        // If ISA is destructing
        if (slowpath(newisa.isDeallocating())) {
            ClearExclusive(&isa.bits);
            if (sideTableLocked) {
                ASSERT(variant == RRVariant::Full);
                sidetable_unlock();
            }
            if (slowpath(tryRetain)) {
                return nil;
            } else {
                return (id)this;
            }
        }
        uintptr_t carry;
        Extra_rc ++ in isa
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++
        //extra_rc takes [57-64] 8 bits, or 2 to the eighth for 256 bits
        // If extra_rc is full (i.e., greater than 256), take half of it and store it in the reference count table in sideTable.
        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if(variant ! = RRVariant::Full) { ClearExclusive(&isa.bits);return rootRetain_overflow(tryRetain);
            }
            // Keep half the reference count and be ready to copy the other half to the hash 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)));/ /.. omit
    return (id)this;
}
Copy the code
  • ifIsa isa pure pointer, then the reference count is storedsidetableIn theReference counter tableIn the
  • Nature of objectWe explored isa ifNot pure pointerFlag bits 57th to 64th store the reference count of the objectextra_rcThat is, extra_RC stores at most 2 to the eighth total256 reference counts
  • ifextra_rcWhen it’s full, it’s greater than 256, so it’sOut of the halfdepositsidetableIn theIn the reference count table

SideTable

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;
   / /...
}
static StripedMap<SideTable>& SideTables() {
    return SideTablesMap.get();
}
class StripedMap {#ifTARGET_OS_IPHONE && ! TARGET_OS_SIMULATOR enum { StripeCount =8 };
#else
    enum { StripeCount = 64 };
#endif
 / /...
}
Copy the code

The SideTable hash table contains a lock slock, a reference count table (Refcnts), a weak_table (Weak_table), and a StripedMap hash table (StripedMap). 64 in other environments. In addition to being stored in the EXTRA_RC of ISA, object reference counts can also be stored in the reference count table of SideTable.

lldbBreak point look at thatextra_rcVerify that reference counts are storedobjectobjInitialization reference count is 1, retain reference count +1, and the total reference count should be 2.objisaMoves to the right of 57Bits are extra_rc, extra_rc has a value of 1, and the reference count equalsextra_rcPlus 1.

A weak reference table

There are two tables in the hash table, one reference counter table and we already know what’s going on, so let’s look at the weak reference table that’s left. Write a demo, create a new NSObject object, weakly reference this object, as follows.

NSObject *obj = [[NSObject alloc] init];
__weak typeof(obj) weakobj=obj;
 NSLog(@"% @",weakobj);
Copy the code

The breakpoint is NSLog to look at the assemblyWeak reference to an object will call objc_initWeak to initialize the object.

id objc_initWeak(id *location, id newObj)
{
    if(! newObj) { *location = nil;return nil;
    }
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}
Copy the code

LLDB breakpoint

objThe pointer andnewObjPointer to the same objectnewObjIs the object that needs a weak reference, andlocationIt should beA pointer to weak references.storeWeakMark is the key function that stores weak references, passing three template variables: there is no old object DontHaveOld= False, there is a new object DoHaveNew= True, in Dealloc you can crash DoCrashIfDeallocating= True, and two actual parametersA pointer to weak referencesandThe object that needs a weak reference.

storeWeak

static id 
storeWeak(id *location, objc_object *newObj)
{
    HaveNew =true haveOld=false
    HaveNew =false,haveOld=true
    ASSERT(haveOld  ||  haveNew);
    if(! haveNew) ASSERT(newObj == nil); Class previouslyInitializedClass = nil; id oldObj; SideTable *oldTable; SideTable *newTable; retry:// If the object is freed, haveOld=true, the weak-reference pointer points to the value that needs to be freed
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
       /* The weak reference table */ is used in the SideTable of the weak reference table. The weak reference table */ is used in the SideTable of the weak reference table
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
    if(haveOld && *location ! = oldObj) {SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }
    if (haveNew  &&  newObj) {
        // Get the class of the object
        Class cls = newObj->getIsa();
        // If the class is not initialized
        if(cls ! = previouslyInitializedClass && ! ((objc_class *)cls)->isInitialized()) {SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); class_initialize(cls, (id)newObj); previouslyInitializedClass = cls; goto retry; }}// When the object is released, the corresponding object and pointer in the weak reference table are deleted
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }
    // Weak pointer is added to the weak reference table
    if (haveNew) {
        /* Weak_register_no_lock parameter: &newTable-> Weak_table: the weak_table address is removed from the SideTable. The pointer to weak reference CrashIfDeallocating: Dealloc releases the Crash flag bit */
       newObj = (objc_object *) weak_register_no_lock(&newTable->weak_table, (id)newObj, location,crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
       
        //newObj is valued and not of type TaggedPointer. Set the weak reference flag bit in the refCount table
        if(! newObj->isTaggedPointerOrNil()) { newObj->setWeaklyReferenced_nolock(); }// Do not set *location anywhere else
        *location = (id)newObj;
    }
    else {
        // No new value, no storage changes
    }
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
    callSetWeaklyReferenced((id)newObj);
    return (id)newObj;
}
Copy the code

Location is the weak reference pointer, newObj is the weak reference object, and the return value of this function is the weak reference object. When a weak reference object is used, newObj is used to find the corresponding hash table SideTable(hash table is a hash table, the real machine has 8 hash tables, other environment has 64 hash tables). Weak_register_no_lock = weak_register_no_lock = weak_register_no_lock = weak_register_no_lock = weak_register_no_lock = weak_register_no_lock = weak_register_no_lock = weak_register_no_lock

weak_register_no_lock

/* Weak_table: weak reference table of the object Referent_id: weak reference object referrer_id: weak reference pointer of the object */
id weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;
    if (referent->isTaggedPointerOrNil()) return referent_id;

    / /... omit
    weak_entry_t *entry;
    // Find the weak reference array entry for the corresponding object in the weak reference table
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        // If there is an array of weak references, add the weak reference pointer to the array
        append_referrer(entry, referrer);
    } 
    else {
        // If the weak-reference array does not exist, create the array, add the weak-reference object and the weak-reference pointer to the array, and add the array to the weak-reference table
        weak_entry_t new_entry(referent, referrer);
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }
    return referent_id;
}
Copy the code

Analysis: First, find the weak reference array entry corresponding to the object in the weak reference table. If the entry is empty, create the array entry, add the weak reference object and weak reference pointer to the array, and add the array to the weak reference table. If entry is not empty, a weak reference pointer is added to the array.

Add weak variable to weak reference table

  • Weak, the underlying callobjc_initWeakTo passWeak reference pointerAnd the need toWeakly referenced objectsAs an argument, and then callstoreWeak
  • storeWeakFunction to find the corresponding object in the hash tableSideTable(There are two tables, reference counting table and weak reference table), from SideTableA weak reference tableAddress, callweak_register_no_lockFunction to add a weak reference variable.
  • weak_register_no_lockFunction, first find the corresponding object in the weak reference tableWeakly referenced arrayEntry, if there is a weak reference array, the weak reference pointerAdd to this arrayIf the weak reference array does not exist, thenCreate this array, add the weak-reference object and weak-reference pointer to the array, and add the array to the weak-reference table

Once an object is freed, how can a weak reference object be freed?

NSObject *obj = [[NSObject alloc] init];
__weak typeof(obj) weakobj=obj;
 obj=nil;
 NSLog(@"% @",weakobj);
Copy the code

Let’s make a breakpoint at NSLog and look at assemblyAfter the weak-referenced object is released, the objc_destroyWeak function is called

void
objc_destroyWeak(id *location){(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);
}
Copy the code

The storeWeak function is also called after the weak-referenced object is freed, which passes three template variables: There is an old object DontHaveOld=true, no new object DoHaveNew=false, no crash DoCrashIfDeallocating=false in Dealloc, and a pointer and nil that are weak references to two actual parameters, StoreWeak function has been analyzed above, and the annotation also explains in detail that if the weak reference object is released, it will first obtain the object pointing to according to the pointer of the weak reference, and then obtain the corresponding weak reference table of the object, and call Weak_unregister_no_lock to subtract the weak reference object.

weak_unregister_no_lock

voidweak_unregister_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;
    weak_entry_t *entry;
    if(! referent)return; / Find the weak reference array entry for the corresponding object in the weak reference tableif ((entry = weak_entry_for_referent(weak_table, referent))) {
        remove_referrer(entry, referrer);
        bool empty = true;
        if(entry->out_of_line() && entry->num_refs ! =0) {
            empty = false;
        }
        else {
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break; }}}if (empty) {
          // Remove the weak reference array from the weak reference tableweak_entry_remove(weak_table, entry); }}}Copy the code

Weak_unregister_no_lock is actually the weak_register_NO_lock function added to weak_register_no_lock object in the weak reference table for subtraction, until the weak reference array in the weak reference table has no value to remove the array from the weak reference table.

conclusion

Weak references resolve circular references because the object’s reference count is not increased, and its memory management is managed through weak reference tables.