Memory management is very important for iOS developers.

Memory structure

Before we talk about memory management, why don’t we take a look at how memory is distributed in iOS

The memory structure is as follows:

  • Code snippet: compiled code
  • Data segment
    • String constants: NSString * STR = @”123″
    • Initialized data: initialized global variables, static variables, etc
    • Uninitialized data: uninitialized global variables, static variables, etc
  • Heap: Dynamically allocated space via alloc, malloc, calloc, etc. The allocated memory space addresses are getting larger and larger
  • Stack: Function call overhead, such as local variables. The allocated memory space address becomes smaller and smaller

Here’s an example:

int a = 10; int b; int main(int argc, char * argv[]) { @autoreleasepool { static int c = 20; static int d; int e; int f = 20; NSString *str = @"123"; NSObject *obj = [[NSObject alloc] init]; NSLog(@"\n&a=%p\n&b=%p\n&c=%p\n&d=%p\n&e=%p\n&f=%p\nstr=%p\nobj=%p\n", &a, &b, &c, &d, &e, &f, str, obj); return UIApplicationMain(argc, argv, nil,NSStringFromClass([AppDelegate class])); }} &a = 0x105604D98 // Initialized global variable, static variable &c = 0x105604d9c // initialized global variable, static variable &b = 0x105604e64 // uninitialized global variable, static variable &d = 0x105604e60 // uninitialized global variable, static variable obj = 0x608000012210 // heap&e = 0x7FFEEA5FCFF4 // stack &f = 0x7FFEEA5FCFF0 // stack STR = 0x105604068 // String constantCopy the code

Memory management

  • 1. Allocate required memory (when initializing values)
  • 2. Use the allocated memory
  • 3. Free its memory when no longer needed (garbage collection)

Memory management focuses on the third phase: garbage collection

Speaking of memory management (garbage collection), there are several ways to manage memory;

Reference counting

The heap area needs to be managed by programmers, so how to manage, record and recycle is a problem worth thinking about.

IOS uses Reference Counting to store the number of times a resource is referenced. When the number of times a resource is referenced becomes zero, the resource space is released for recycling.

For early iOS, Mannul Reference Counting (MRC) was used to manage Reference Counting manually, and methods such as retain and release were inserted to manage the life cycle of objects. However, because MRC is too troublesome to maintain, the 2011 WWDC conference proposed ARC (Automatic Reference Counting) Automatic Reference Counting management, through the static analysis of the compiler, Automatic insertion of Counting management logic, so as to avoid complex manual management.

ARCIs a compiler feature, but the compiler at the corresponding time to give us memory management code, its essence is still according toMRCThe rules of

Mark-clear

In other programming languages, in addition to reference counting, there is a mark-clear algorithm

This algorithm simplifies the definition of “whether an object is no longer needed” to “whether an object is available”. The marking phase is the phase in which all live objects are marked. The cleanup phase is the phase in which unmarked objects, i.e. inanimate objects, are reclaimed. Through these two phases, unused memory space can be reclaimed

This algorithm assumes setting up an object called root (in Javascript, root is the global object). The garbage collector will periodically start at the root, find all the objects referenced from the root, and then find the objects referenced by those objects… Starting at the root, the garbage collector finds all reachable objects and collects all unreachable objects.

Reachable Algorithm (Tracing GC)

I won’t say much here.

In contrast, reference counting, because it only records the number of times an object is referenced, is actually only a local piece of information, and lacks global information, so it can cause problems with circular references and requires special attention at the code level.

So why does iOS use reference counting?

By using reference counting first, objects can be reclaimed immediately at the end of their life cycle, rather than having to wait for global traversal to go back. Secondly, in the case of insufficient memory, the reachable algorithm has higher latency and lower efficiency. Since the overall memory of iPhone is relatively small, reference counting is a more reasonable choice.

Tagged Pointer

Before we talk about reference counting, let’s start with an outlaw: Tagged Pointer

  • Starting from 64-bit, iOS introduces Tagged Pointer technology to optimize storage of small objects such as NSNumber, NSDate, and NSString

  • Before Tagged Pointer is used, objects such as NSNumber need to dynamically allocate memory and maintain reference counting. The NSNumber Pointer stores the address value of the NSNumber object in the heap

  • After using Tagged Pointer, the Data stored in the NSNumber Pointer becomes Tag + Data, that is, the Data is stored directly in the Pointer

  • When Pointers are insufficient to store data, dynamically allocated memory is used to store data

  • Objc_msgSend can recognize Tagged Pointer, such as the intValue method of NSNumber, and directly extract data from Pointer, saving previous call overhead

  • How do I check whether a Pointer is Tagged Pointer?

    • IOS platform, the highest significant bit is 1 (64bit)
    • Mac platform, the least significant bit is 1

Check if it is Tagged Pointer code

int main(int argc, const char * argv[]) {
@autoreleasepool {

    NSNumber *number1 = @4;
    NSNumber *number2 = @5;
    NSNumber *number3 = @(0xFFFFFFFFFFFFFFF);

    NSLog(@"\nnumber1=%p\nnumber2=%p\nnumber3=%p", number1, number2, number3);

    }
    return 0;
}

打印:
number1=0xb327e58317e80524
number2=0xb327e58317e80534
number3=0x600000f2a220
Copy the code
NSString *str1 = [NSString stringWithFormat:@"abc"]; NSString *str2 = [NSString stringWithFormat:@"abcdefghijk"]; NSLog(@"\n[str1 class]=%@\n[str2 class]=%@",[str1 class],[str2 class]); Print: [str1 class]=NSTaggedPointerString [str2 class]=__NSCFStringCopy the code

Str1 is of type NSTaggedPointerString and does not use set to find objects.

We can also find the implementation in the source code,

1. Find the implementation of the retain method in nsobject. mm

- (id)retain {
    return ((id)self)->rootRetain();
}
Copy the code

If (isTaggedPointer()) return (id)this; if (isTaggedPointer()) return (id)this; That is, if it is a TaggedPointer type, it is returned directly without looking up the pointer.

Q: What is the difference between the printed results of these two pieces of code?

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

for (int i = 0; i < 100; i++) {
    dispatch_async(queue, ^{
        self.name = [NSString stringWithFormat:@"abcdefghijk"];
    });
}
Copy the code
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    for (int i = 0; i < 100; i++) {
    dispatch_async(queue, ^{
        self.name = [NSString stringWithFormat:@"abc"];
    });
}
Copy the code

Memory management of OC objects

In iOS, reference counting is used to manage the memory of OC objects

The default reference count of a newly created OC object is 1. When the reference count is reduced to 0, the OC object is destroyed, freeing up its memory

A call to retain gives the OC object’s reference count +1, and a call to release gives the OC object’s reference count -1

Memory management lessons learned: When alloc, new, copy, and mutableCopy methods return an object, call release or autorelease when the object is no longer needed. If you want to own an object, make its reference count +1. If you don’t want to own an object, let its reference count be -1

copy

Why use copy?

  • 1. The purpose of copy: to produce a copy object, which does not affect the source object
    • The source object has been modified without affecting the replica object
    • The replica object is modified without affecting the source object
  • IOS provides two copy methods
    • 1, copy, immutable copy, produce immutable copy
    • 2, mutableCopy, produce mutableCopy
  • 3. Deep and shallow copies
    • 1. Deep copy: copy content to generate new objects
    • 2, shallow copy: pointer copy, no new object generated
NSString & NSMutableString
  • rightImmutable stringforcopy&mutableCopyoperation
void test1() { NSString *str1 = [NSString stringWithFormat:@"test"]; NSString *str2 = [str1 copy]; NSMutableString *str3 = [str1 mutableCopy]; // return NSMutableString NSLog(@"%p %p %p", str1, str2, str3); } print 0xaa073e462fbfcdc7 0xAA073e462fBFcdc7 0x282143150Copy the code

According to the printed address, it can be seen that the immutable string is a shallow copy when it is copied, only the pointer is copied without the object. MutableCopy is a deep copy that makes a new object

Note: If str1 is short, it might use TaggedPointer, not the object type

  • rightMutable stringforcopy&mutableCopyoperation
void test2() { NSMutableString *str1 = [[NSMutableString alloc] initWithFormat:@"test"]; // 1 NSString *str2 = [str1 copy]; NSMutableString *str3 = [str1 mutableCopy]; // deep copy NSLog(@"%p %p %p", str1, str2, str3); } print 0x281DD76c0 0x83e9bdf3daead453 0x281DD7a50Copy the code

Both copy and mutableCopy are deep copies of mutable strings, according to the printed address

NSArray & NSMutableArray
  • Copy&mutableCopy operation on an immutable array
void test3() { NSArray *array1 = [[NSArray alloc] initWithObjects:@"a", @"b", nil]; NSArray *array2 = [array1 copy]; Array3 = [array1 mutableCopy]; // deep copy NSLog(@"%p %p %p", array1, array2, array3); } print 0x2823AC740 0x2823AC740 0x282DD6CA0Copy the code

According to the printed address, it can be seen that the immutable group is a shallow copy, only the pointer is copied, but no object is copied. MutableCopy is a deep copy that makes a new object

  • Copy&mutableCopy on mutable arrays
void test4() { NSMutableArray *array1 = [[NSMutableArray alloc] initWithObjects:@"a", @"b", nil]; NSArray *array2 = [array1 copy]; // NSMutableArray *array3 = [array1 mutableCopy]; // deep copy NSLog(@"%p %p %p", array1, array2, array3); } 0x282993600 0x2827FC760 0x282993270Copy the code

Both copy and mutableCopy are deep copies of mutable arrays

NSDictionary & NSMutableDictionary
  • Copy&mutableCopy on an immutable dictionary
void test5() { NSDictionary *dict1 = [[NSDictionary alloc] initWithObjectsAndKeys:@"jack", @"name", nil]; NSDictionary *dict2 = [dict1 copy]; // NSMutableDictionary *dict3 = [dict1 mutableCopy]; // NSLog(@"%p %p %p", dict1, dict2, dict3); } print 0x2824DFbc0 0x2824DFbc0 0x2824DFB20Copy the code

According to the printed address, it can be seen that immutable dictionary is a shallow copy when it is copied, only Pointers are copied without objects. MutableCopy is a deep copy that makes a new object

  • Copy and copy mutable dictionary
void test6() { NSMutableDictionary *dict1 = [[NSMutableDictionary alloc] initWithObjectsAndKeys:@"jack", @"name", nil]; NSDictionary *dict2 = [dict1 copy]; // NSMutableDictionary *dict3 = dict1 mutableCopy]; // NSLog(@"%p %p %p", dict1, dict2, dict3); } print 0x283e50c60 0x283e50bc0 0x283e50ba0Copy the code

Both copy and mutableCopy are deep copies of mutable arrays

Copy ordinary class objects?

If you want to copy your own objects, you need to implement the NSCopying protocol. NSCopying and NSMutableCopying protocols are implemented simultaneously if the custom object is of mutable and immutable versions.

  • 1. Declare that this class complies with NSCopying
  • 2. Implement NSCopying protocol- (id)copyWithZone:(NSZone *)zone;
  • 3, in- (id)copyWithZone:(NSZone *)zone;Method to reassign a class object

demo:

@interface Dog : NSObject<NSCopying> @property (nonatomic,assign) int age; @property (nonatomic,copy) NSString *name; @end - (id)copyWithZone:(NSZone *)zone{ Dog *d = [[self class]allocWithZone:zone]; d.age = _age; d.name = _name; return d; } - (void)setName:(NSString *)name { if (_name ! = name) { //[_name release]; //MRC _name = [name copy]; }}Copy the code

aboutcopywithmutablCopyConclusion:

assign weak

Assign is used to modify basic data types, including basic data types (NSInteger, CGFloat) and C data types (int, float, double, char, and so on).

The assign attribute does not increase the reference count. Once the assigned attribute is assigned, it will not be used by other objects.

If a new object is allocated to this memory address, it will crash again. Therefore, we usually only declare basic data types, because they will be allocated to the stack, and the stack will be handled by the system automatically, so there is no wild pointer

Weak and assign

  • 1. Modify the difference between variable types

    • weakOnly objects can be decorated.The compiler will report an error if you modify basic data typesProperty with ‘weak’ attribute must be of object type.
    • assignModifiable objects, and primitive data types. Used in the MRC era when you need to modify object typesunsafe_unretained. Of course,unsafe_unretainedIt can also produce wild Pointers, so its name isunsafe_.
  • 2. Whether to generate wild Pointers

    • weakThere is no wild pointer problem. becauseweakWhen the modified object is freed (with a reference counter value of 0), the pointer is automatically set to nil, and subsequent messages to the object do not crash. Weak is safe.
    • assignIf you modify an object, there will be a wild pointer problem; It is safe to modify primitive data types. The pointer is not automatically null after the decorated object is released, and sending messages to the object crashes.

The key is the address of the specified object and the value is the address array of the weak pointer

Weak implementation principle summary

The Runtime maintains a weak table that stores all the weak Pointers to an object. The weak table is actually a hash table, where Key is the address of the specified object and Value is an array of addresses of the specified object

The implementation principle of weak can be summarized in three steps:

  • 1. Initialization: The Runtime calls the objc_initWeak function and initializes a new weak pointer to the address of the object.

  • 2. When adding a reference: the objc_initWeak function will call objc_storeWeak(), which updates the pointer pointer and creates the corresponding weak reference table.

  • 3. When releasing, call the clearDeallocating function. The clearDeallocating function first fetches an array of weak pointer addresses based on the object’s address, then iterates through the array to set it to nil, deletes the entry from the Weak table, and clears the object’s record.

In the nsobject. mm file, there is an objc_initWeak method, which is the weak initialization function

id objc_initWeak(id *location, id newObj) { if (! NewObj) {// Invalid object directly causes pointer to be freed *location = nil; return nil; } return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj); }Copy the code

Below is the update pointer to create the corresponding weak reference table

static id storeWeak(id *location, objc_object *newObj) { ASSERT(haveOld || haveNew); if (! haveNew) ASSERT(newObj == nil); Class previouslyInitializedClass = nil; id oldObj; SideTable *oldTable; SideTable *newTable; Retry: if (haveOld) {// Change the pointer to oldObj = *location; oldTable = &SideTables()[oldObj]; } else { oldTable = nil; } if (haveNew) {newTable = &SideTables()[newObj]; } else { newTable = nil; SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable); If (haveOld && *location!) {// If (haveOld && *location! = oldObj) { SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); goto retry; } if (haveNew && newObj) { Class cls = newObj->getIsa(); if (cls ! = previouslyInitializedClass && ! ((objc_class *)cls)->isInitialized()) { SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); // unlock class_initialize(CLS, (id)newObj); // If the class has already completed, the +initialize method is ideal // if the class +initialize is in a thread // for example, +initialize is calling the storeWeak method // Need to manually add protection strategy, and set the mark previouslyInitializedClass = CLS previouslyInitializedClass pointer; goto retry; If (haveOld) {weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); if (haveOld) {weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); If (haveNew) {newObj = (objc_object *) weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating); if (! newObj->isTaggedPointerOrNil()) { newObj->setWeaklyReferenced_nolock(); } *location = (id)newObj; } else { // No new value. The storage is not changed. } SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); callSetWeaklyReferenced((id)newObj); return (id)newObj; }Copy the code

View the structure of the SideTable

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

You can see that the SideTable is a structure

  • 1.spinlock_t slock;A spin lock that guarantees atomic operation
  • 2,RefcountMap refcnts;Hash table for reference counting
  • 3,weak_table_t weak_table;Weak References the global hash table

Let’s look at weak_table_t as a hash table

struct weak_table_t { weak_entry_t *weak_entries; // Save all weak Pointers to the specified object size_t num_entries; // Uintptr_t mask; // Uintptr_t max_hash_displacement; // Maximum offset of hash key};Copy the code

This is a globally weak reference hash table. Use the address of the object as the key and the weak_entry_t type structure object as the value

Consider weak_entry_t

struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line_ness : 2;
            uintptr_t        num_refs : PTR_MINUS_2;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
};
Copy the code

Weak_entry_t is an internal structure stored in a weak-reference table that is responsible for maintaining and storing all weak-reference hash tables that point to an object

clearDeallocating

Objc_clear_deallocating does the following:

  • 1. Obtain the address of the abandoned object from the weak table
  • 2. Assign nil to all addresses with weak modifiers included in the record
  • 3. Delete the weak table from the weak table
  • Delete the address of the discarded object from the reference count table
NEVER_INLINE void objc_object::clearDeallocating_slow() { ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc)); SideTable& table = SideTables()[this]; Table. Lock (); table. Lock (); Weakly_referenced) {// if (isa.weakly_referenced) {// if there isa reference count weak_clear_no_lock(&table. Weak_table, (id)this); } if (isa.has_sidetable_rc) { table.refcnts.erase(this); } table.unlock(); }Copy the code

In clearDeallocating_slow, the record of obtaining the address of the abandoned object as the key value in the weak table is first found, and then the weak_clear_NO_lock function is called to clear the operation

void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) { objc_object *referent = (objc_object *)referent_id; Weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); if (entry == nil) { return; } // zero out references weak_referrer_t *referrers; size_t count; if (entry->out_of_line()) { referrers = entry->referrers; count = TABLE_SIZE(entry); } else { referrers = entry->inline_referrers; count = WEAK_INLINE_COUNT; } for (size_t i = 0; i < count; ++i) { objc_object **referrer = referrers[i]; if (referrer) { if (*referrer == referent) { *referrer = nil; // Clear object, assign nil}else if (*referrer) {objc_weak_error(); Weak_entry_remove (weak_table, entry); weak_entry_remove(weak_table, entry); weak_entry_remove(weak_table, entry); }Copy the code

dealloc

You can find the dealloc function in nsobject. mm

- (void)dealloc { _objc_rootDealloc(self); } void _objc_rootDealloc(id obj) { ASSERT(obj); obj->rootDealloc(); } inline void objc_object::rootDealloc() { if (isTaggedPointer()) return; // fixme necessary? if (fastpath(isa.nonpointer && ! isa.weakly_referenced && ! isa.has_assoc && #if ISA_HAS_CXX_DTOR_BIT ! isa.has_cxx_dtor && #else ! isa.getClass(false)->hasCxxDtor() && #endif ! isa.has_sidetable_rc)) { assert(! sidetable_present()); free(this); } else { object_dispose((id)this); }}Copy the code

As can be seen:

  • 1, first judge whether the object isisTaggedPointerIf it isTaggedPointerSo no reference counting technique is used, so return directly
  • 2, notTaggedPointerDetermines whether there isa weak reference, an associated object, a destructor, a reference counter that is too large to be stored in isa, and then destroys the objectobject_dispose
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(); // This order is important. if (cxx) object_cxxDestruct(obj); if (assoc) _object_remove_assocations(obj, /*deallocating*/true); // Clear the member variable obj->clearDeallocating(); // set weak reference pointer to current object to nil} return obj; }Copy the code

As can be seen:

  • 1, determine whether there is a destructor, whether there is an associated object
  • 2. If there is a destructor, destroy it. If there are associated objects, clear them.
  • Set a weak reference pointer to the current object to nil

atomic

When we talked about locks earlier, we mentioned atomic

Atomic is used to ensure atomicity of property setters and getters. It is equivalent to placing a thread synchronization lock inside the getter and setter

See objC-accessors.mm of objC4, which does not guarantee that the process of using attributes is thread-safe

Related source code:

//objc-accessors.mm id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) { if (offset == 0) { return object_getClass(self); } // Retain Release world // If not atomic, return id* slot = (id*) ((char*)self + offset); if (! atomic) return *slot; // Atomic retain Release world // If Atomic, lock spinlock_t& slotlock = PropertyLocks[slot]; slotlock.lock(); id value = objc_retain(*slot); slotlock.unlock(); // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock. return objc_autoreleaseReturnValue(value); } static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) { if (offset == 0) { object_setClass(self, newValue); return; } id oldValue; id *slot = (id*) ((char*)self + offset); if (copy) { newValue = [newValue copyWithZone:nil]; } else if (mutableCopy) { newValue = [newValue mutableCopyWithZone:nil]; } else { if (*slot == newValue) return; newValue = objc_retain(newValue); } // If it is not atomic, set newValue if (! atomic) { oldValue = *slot; *slot = newValue; } else {// If atomic, use spinlock_t spinlock_t& slotlock = PropertyLocks[slot]; slotlock.lock(); oldValue = *slot; *slot = newValue; slotlock.unlock(); } objc_release(oldValue); }Copy the code

Atomic has a spinlock_t control in the getter/setter of the object. The above two functions are, so to speak, the underlying implementation of setter ‘and getter methods.

autoreleasepool

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[[Person alloc]init] autorelease];
    }
        return 0;
}
Copy the code

Convert main.m to C++ code using the xcrun-sdk iphoneos clang-arch arm64-rewrite-objc main.m command

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

    Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));

    }
    return 0;
}
Copy the code

Autoreleasepool __AtAutoreleasePool __autoreleasepool;

__AtAutoreleasePool is a structure that we find globally

Struct __AtAutoreleasePool {__AtAutoreleasePool() {// constructor, Atautoreleasepoolobj = objc_autoreleasePoolPush(); } ~__AtAutoreleasePool() {// Call objc_autoreleasePoolPop(atAutoReleasepoolobj) when the structure is destroyed; } void * atautoreleasepoolobj; };Copy the code

Replace it with:

@autoreleasepool { Person *p = [[[Person alloc]init] autorelease]; } ## atAutoReleasepoolobj = objc_autoreleasePoolPush(); Person *person = [[[Person alloc] init] autorelease]; objc_autoreleasePoolPop(atautoreleasepoolobj);Copy the code

The implementations of objc_autoreleasePoolPush and objc_autoreleasePoolPop can be found in the Runtime source code

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

NEVER_INLINE
void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}
Copy the code

The AutoreleasePoolPage class is used to call both the push() and pop(CTXT) functions.

AutoreleasePoolPage

For the AutoreleasePoolPage class, look at member variables, or for explorations where static light is not enough.

Class AutoreleasePoolPage: private AutoreleasePoolPageData {// Magic_t const magic; // The AutoreleasePoolPage structure is complete. id *next; // points to the next location of the newly added Autoreleased object, initialized to begin; pthread_t const thread; // point to the current thread; AutoreleasePoolPage * const parent; // The parent of the first AutoreleasePoolPage node is nil; AutoreleasePoolPage *child; // Point to a child. The last AutoreleasePoolPage child is nil; uint32_t const depth; // The depth of the current node starts at 0 and increases later; uint32_t hiwat; Hige water mark hige water mark / /... }Copy the code
  • 1, eachAutoreleasePoolPage objectTake up4096Byte memory, in addition to storing its internal member variables, the remaining space is used to store the address of the AutoRelease object
  • 2. All of themAutoreleasePoolPageObject throughTwo-way linked listThe form is connected together
  • 3. Calling the push method throws aPOOL_BOUNDARYAnd returns the memory address where it is stored
  • 4. Pass one when calling the POP methodPOOL_BOUNDARYWill send a release message from the last object pushed until this one is encounteredPOOL_BOUNDARY
  • 5,id *nextIt points to the next one that can be storedAddress of the autoRelease objectThe area
  • 6,AutoreleasePoolPageWhen the space is full, a new link is created in the form of a linked listAutoreleasePoolPageObject, and then willThe address of the new AutoRelease objectThere arechildPointer to the

The push() function is implemented
static inline void *push() { id *dest; if (DebugPoolAllocation) { // Each autorelease pool starts on a new pool page. dest = autoreleaseNewPage(POOL_BOUNDARY);  } else { dest = autoreleaseFast(POOL_BOUNDARY); } assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY); return dest; }Copy the code
  • 1, inDebugPoolAllocationCall when the thread pool is fullautoreleaseNewPage(POOL_BOUNDARY)To create a new thread pool.
  • Called when the thread pool is not fullautoreleaseFastFunction that is pushed into the thread pool as a stack.
static inline id *autoreleaseFast(id obj)
{
    AutoreleasePoolPage *page = hotPage();
    if (page && !page->full()) {
        return page->add(obj);
    } else if (page) {
        return autoreleaseFullPage(obj, page);
    } else {
        return autoreleaseNoPage(obj);
    }
}
Copy the code
  • There arehotPageAnd the currentDissatisfaction with the page, the callpage->add(obj) Method to add an object toAutoreleasePoolPageThe stack
  • There arehotPageAnd the currentThe page is full, the callautoreleaseFullPageTo initialize a new page, callpage->add(obj) Method to add an object toAutoreleasePoolPageThe stack
  • There is nohotPage, the callautoreleaseNoPagE Create ahotPage, the callpage->add(obj) Method to add an object toAutoreleasePoolPageThe stack
Pop () function
Static inline void pop(void *token) {AutoreleasePoolPage *page; id *stop; page = pageForPointer(token); stop = (id *)token; POOL_BOUNDARY release 'autoreleased' object page->releaseUntil(stop); // hysteresis: keep one empty child if page is more than half full // 2. After releasing the 'Autoreleased' object, the extra pages are destroyed. if (page->lessThanHalfFull()) { page->child->kill(); } else if (page->child->child) { page->child->child->kill(); }}Copy the code

Come to releaseUntil (…). Internal:

Void releaseUntil(id *stop) {// 1. While (this->next! = stop) { AutoreleasePoolPage *page = hotPage(); // 2. while (page->empty()) { page = page->parent; setHotPage(page); } // 3. if (obj ! = POOL_BOUNDARY) { objc_release(obj); } } // 4. setHotPage(this); }Copy the code
  • 1, the external loop traverses one by oneautoreleasedObject until traversed tostopthisPOOL_BOUNDARY
  • 2. If the currenthatPageThere is noPOOL_BOUNDARYThat will behotPageSet to the parent node.
  • 3, give the presentautoreleasedObject to sendreleaseThe message.
  • 4. Configure againhotPage.
int main(int argc, const char * argv[]) {
	@autoreleasepool {//r1 = push()
		Person *p1 = [[[Person alloc]init] autorelease];
		Person *p2 = [[[Person alloc]init] autorelease];
		@autoreleasepool {//r2 = push()
			Person *p3 = [[[Person alloc]init] autorelease];
			@autoreleasepool {//r3 = push()
					Person *p4 = [[[Person alloc]init] autorelease];
					_objc_autoreleasePoolPrint();
				}//pop(r3)
		}//pop(r2)
	}//pop(r1)

	return 0;
}
Copy the code

After each Push, a POOL_BOUNDARY will be added to hold the position, in order to correspond to the release of a Pop. For example, page in the figure requires two Pop and then complete release

Extern void _objc_autoreleasePoolPrint(void); (run in command line project) as shown below:

_objc_autoreleasePoolPrint source code:

void 
_objc_autoreleasePoolPrint(void)
{
    AutoreleasePoolPage::printAll();
}

 static void printAll()
    {        
        _objc_inform("##############");
        _objc_inform("AUTORELEASE POOLS for thread %p", pthread_self());

        AutoreleasePoolPage *page;
        ptrdiff_t objects = 0;
        for (page = coldPage(); page; page = page->child) {
            objects += page->next - page->begin();
        }
        _objc_inform("%llu releases pending.", (unsigned long long)objects);

        if (haveEmptyPoolPlaceholder()) {
            _objc_inform("[%p]  ................  PAGE (placeholder)", 
                         EMPTY_POOL_PLACEHOLDER);
            _objc_inform("[%p]  ################  POOL (placeholder)", 
                         EMPTY_POOL_PLACEHOLDER);
        }
        else {
            for (page = coldPage(); page; page = page->child) {
                page->print();
            }
        }

        _objc_inform("##############");
    }
Copy the code

AutoreleasePool and Runloop

- (void)viewDidLoad {
    [super viewDidLoad];

    Person *p = [[[Person alloc]init] autorelease];
    NSLog(@"%s",__func__);
}
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    NSLog(@"%s", __func__);
}
- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    NSLog(@"%s", __func__);
}
Copy the code

Printing in MRC environment:

-[ViewController viewDidLoad]
-[ViewController viewWillAppear:]
-[Person dealloc]
-[ViewController viewDidAppear:]
Copy the code

Printing in ARC environment:

-[ViewController viewDidLoad]
-[Person dealloc]
-[ViewController viewWillAppear:]
-[ViewController viewDidAppear:]
Copy the code

Person is obviously released earlier in ARC than in MRC, and you can guess that in ARC, the compiler is going to do something for us, which is release before viewDidLoad ends

- (void)viewDidLoad {
    [super viewDidLoad];
    Person *p = [[Person alloc]init];
    NSLog(@"%s",__func__);
    [p release];
}
Copy the code

Let’s print the currentRunLoop [NSRunLoop currentRunLoop]

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *p = [[[Person alloc]init] autorelease];
   	
 	NSLog(@"%@", [NSRunLoop mainRunLoop]);
}
Copy the code

You can see the callout is _wrapRunLoopWithAutoreleasePoolHandler

0x1 = 1,0 xa0=160 = 32+128

Enumeration by state of RunLoop

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),              // 1
    kCFRunLoopBeforeTimers = (1UL << 1),       // 2
    kCFRunLoopBeforeSources = (1UL << 2),      // 4
    kCFRunLoopBeforeWaiting = (1UL << 5),      // 32
    kCFRunLoopAfterWaiting = (1UL << 6),       // 64
    kCFRunLoopExit = (1UL << 7),               // 128
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
Copy the code

Specific steps

  • IOS has registered two observers in the main thread Runloop
  • 2, The first Observer listenedkCFRunLoopEntryEvent will be calledobjc_autoreleasePoolPush()
  • 3. The second Observer listenedkCFRunLoopBeforeWaitingEvent will be calledobjc_autoreleasePoolPop(),objc_autoreleasePoolPush() ; Listen to thekCFRunLoopBeforeExitEvent will be calledobjc_autoreleasePoolPop()

The AutoReleased object was released when runloop was about to go to sleep

Let’s look at child threads:

The primary runloop is enabled by default, and the child runloop is disabled by default. This means that autoReleasepool will not be created in the child thread, so we need to create an autoreleasepool in the child thread. (The class methods used in the child thread are all autoRelease, so there is no pool torelease, which means there is no way torelease later, causing a memory leak.)

If an event occurs in the main thread, then runloop will go back to create autoreleasepool. This explains why the autoreleasepool is not created in the child thread. The child thread’s runloop is closed by default, so it does not automatically create autoreleasepool. We need to manually add;

For NSThread and NSOperationQueue, autoReleasepool needs to be manually created. For GCD, autoReleasepool does not need to be manually created because each GCD queue creates its own AutoReleasepool

If there is any mistake above, welcome to correct. Please indicate the source of reprint.