Memory management portal 🦋🦋🦋

Memory management analysis (a) – MANUAL memory management in the MRC era

Memory management analysis (two) – timer problem

Memory management analysis (three) – iOS program memory layout

Memory management analysis (four) — Autorelease principle analysis

Storage of iOS reference counts

As I explained in my isa insight article, Apple has optimized ISA starting with the ARM64 architecture to store more information in isa Pointers through bit-field counting, making full use of ISA memory space. The current structure of ISA is as follows

union isa_t 
{
    Class cls;
    uintptr_t bits;
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
    };

Copy the code

The EXTRA_rc is used to store reference counts, using the 19 binary values above ISA as storage space. The extra_rc name means the extra reference count, which is the number of times an object has retained at any other time than the retain operation at creation time. So the actual reference count of an object = EXTRA_rc + 1 (the time it was created). Of course, extra_RC is limited in how many expressions it can express. When a reference to an object exceeds the extra_RC representation scope, the has_sideTABLE_rc inside ISA indicates that an object’s reference count cannot be stored in ISA. And stores the reference count value in a property of a class called SideTable. The definition of SideTable can be found in the objC source nsobject.mm file. The following

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;
    }
Copy the code

If isa does not store reference counts, the reference counts are stored in the Refcnts of the SideTable, which is actually a hash table structure (similar to a dictionary in OC) as shown by the type RefcountMap.

Weak pointer implementation principle

Across the property, you can set strong, weak, and unsafe_unretained, and translate this to __strong, __weak, and __unsafe_unretained. Let’s take a look at the differences, one by one, through the following code examples and the results

*********************mian.m***************************
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSLog(@"Temporary scope begins");
        {
            CLPerson *person = [[CLPerson alloc] init];
            NSLog(@"Person object: %@", person);
		}
        NSLog(@"Temporary scope ends");
		
		NSLog(@"StrongPerson: % @", strongPerson);
            
    }
    return 0; } ******************* Result ********************2019- 09. 19:59:00.835983+0800Block learning [24021:2941713The temporary scope begins2019- 09. 19:59:00.836482+0800Block learning [24021:2941713] person object: <CLPerson:0x100704a60>
2019- 09. 19:59:00.836541+0800Block learning [24021:2941713] -[CLPerson dealloc]
2019- 09. 19:59:00.836575+0800Block learning [24021:2941713] Temporary scope ended Program withexit code: 0
Copy the code

The above example makes it clear that the local variable person is released when it is out of temporary scope.


*********************mian.m***************************
int main(int argc, const char * argv[]) {
    @autoreleasepool {
      
        __strong CLPerson *strongPerson;
        
        NSLog(@"Temporary scope begins");
        {
            CLPerson *person = [[CLPerson alloc] init];
            NSLog(@"Person object: %@", person);
            strongPerson = person;
		}
        NSLog(@"Temporary scope ends");
            
        NSLog(@"StrongPerson: % @", strongPerson);
    }
    return 0; } ******************* Result ********************2019- 09. 20:00:07.368972+0800Block learning [24033:2942509The temporary scope begins2019- 09. 20:00:07.369392+0800Block learning [24033:2942509] person object: <CLPerson:0x1007003c0>
2019- 09. 20:00:07.369430+0800Block learning [24033:2942509] the temporary scope ends2019- 09. 20:00:07.369442+0800Block learning [24033:2942509] strongPerson: < CLPerson:0x1007003c0>
2019- 09. 20:00:07.369460+0800Block learning [24033:2942509] -[CLPerson dealloc]
Program ended with exit code: 0
Copy the code

When a person is pointed to by an out-of-scope __strong pointer, you can see that the person object is not destroyed after the temporary scope ends, indicating that the __strong pointer increases the reference count of person


*********************mian.m***************************
int main(int argc, const char * argv[]) {
    @autoreleasepool {
      
        __weak CLPerson *weakPerson;
                
        NSLog(@"Temporary scope begins");
        {
            CLPerson *person = [[CLPerson alloc] init];
            NSLog(@"Person object: %@", person);
            weakPerson = person;
		}
        NSLog(@"Temporary scope ends");
        
        NSLog(@"WeakPerson: % @", weakPerson);    
    }
    return 0; } ******************* Result ********************2019- 09 -02 21:48:58.332332+0800 Block learning [24180:2987983The temporary scope begins2019- 09 -02 21:48:58.332851+0800 Block learning [24180:2987983] person object: <CLPerson:0x100600d20>
2019- 09 -02 21:48:58.332889+0800 Block learning [24180:2987983] -[CLPerson dealloc]
2019- 09 -02 21:48:58.332920+0800 Block learning [24180:2987983] the temporary scope ends2019- 09 -02 21:48:58.332938+0800 Block learning [24180:2987983] weakPerson: (null)
Program ended with exit code: 0
Copy the code

When a person is pointed to by an out-of-scope __weak pointer, you can see that when the temporary scope ends, the person is freed as in the first case, indicating that the __weak pointer does not increase the reference count of person, and the __weak pointer is set to nil when the person is freed. Wild pointer errors are prevented


*********************mian.m***************************
int main(int argc, const char * argv[]) {
    @autoreleasepool {
      
        __unsafe_unretained CLPerson *unsafePerson;
        
        NSLog(@"Temporary scope begins");
        {
            CLPerson *person = [[CLPerson alloc] init];
            NSLog(@"Person object: %@", person);
			unsafePerson = person; 
		}
        NSLog(@"Temporary scope ends");
        
        NSLog(@"UnsafePerson: % @", unsafePerson);    
    }
    return 0; } ******************* Result ********************Copy the code

personBeing extraterritorial__unsafe_unretainedWhen the pointer points, you can see that after the temporary scope ends,personJust like in the first case, it’s just released__unsafe_unretainedThe Pointers are not increasingpersonThe reference count, however, appears in the endEXC_BAD_ACCESSAn error is reported, indicating a wild pointer problem.

__weak and __unsafe_unretained are different because __weak is automatically set to nil upon release, but __unsafe_unretained is not. So how does Apple automatically clear the __weak pointer after an object is freed? Let’s dig from the source code.

Since the __weak pointer is cleared when the object is released, we start with the object’s dealloc method. We can find the implementation of dealloc in objc source nsobjject. Mm as follows

- (void)dealloc { _objc_rootDealloc(self); } * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *void_objc_rootDealloc(id obj) { assert(obj); obj->rootDealloc(); } * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *inline void objc_object::rootDealloc(a)
{
	//🥝 If it is Tagged Pointer, return directly
    if (isTaggedPointer()) return;  // fixme necessary?

	/* 🥝 if (1) the isa is optimized, (2) the weak pointer is not referenced, (3). With no associated objects, no C++ destructors, and no sideTable, you can free() */
    if(fastpath(isa.nonpointer && ! isa.weakly_referenced && ! isa.has_assoc && ! isa.has_cxx_dtor && ! isa.has_sidetable_rc)) { assert(! sidetable_present());free(this);
    } 
    else {// If not, we need to use the following function
        object_dispose((id)this); }}Copy the code

Enter the object_dispose function

id object_dispose(id obj)
{
    if(! obj)return nil;

    objc_destructInstance(obj);    
    free(obj);

    returnnil; } * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        🥝 if there is a C++ destructor, call it
        if (cxx) object_cxxDestruct(obj);
        //🥝 Remove the associated object if it exists
        if (assoc) _object_remove_assocations(obj);
        // call the following function when 🥝 is done
        obj->clearDeallocating();
    }

    returnobj; } * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *inline void  objc_object::clearDeallocating(a)
{
    if(slowpath(! isa.nonpointer)) {// Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.clearDeallocating_slow(); } assert(! sidetable_present()); }Copy the code

In the above method, ifisaIf it is a normal pointer, call it directlysidetable_clearDeallocatingDelta function, if it’s a delta functionoptimizedtheisaThen goclearDeallocating_slowFunction. Let’s look at these two functions You can see that both functions are internally calledweak_clear_no_lock(&table.weak_table, (id)this);To deal with__weakPointer, where the first argument issideTableA member of theweak_tableThe second argument is the object to be released. Let’s look at the internal logic of the functionThe core approach here isweak_entry_for_referentOrder inObviously, in the above method, through the object to be releasedreferentAn index is obtained according to a certain algorithmindexAnd then fromweak_tableUse insideindexTo get the objectreferentThe correspondingweakThe pointer, that showsweak_tableThe inside is just a hash table structure that passesobjectAs akey.valueThat’s pointing to thatobjecttheweakAn array of Pointers.

Weak implementation principle summary

  • When an objectobjbeweakWhen the pointer points to thisweakA pointer toobjAs akeyIs stored tosideTableOf the classweak_tableThis hash table contains an array of weak Pointers.
  • When an objectobjthedeallocWhen the method is called, the Runtime returns theobjforkey, fromsideTabletheweak_tableIn the hash table, find the correspondingweakPointer to the list and then place the insideweakSet the Pointers one by one tonil.

This is how the weak pointer is implemented

Memory management portal 🦋🦋🦋

Memory management analysis (a) – MANUAL memory management in the MRC era

Memory management analysis (two) – timer problem

Memory management analysis (three) – iOS program memory layout

Memory management analysis (four) — Autorelease principle analysis