Weak pointer

Weak references to variables are usually made using __weak, and variables decorated with __weak are automatically set to nil once released

__unsafe_unretained also works as a weak pointer, but it differs from __weak in that the modified variable is not set to nil upon release

Weak implementation principle

The handling of weak references can be found in the implementation of the Dealloc destructor

Dealloc -> _objc_rootDealloc -> rootDealloc -> object_Dispose -> objc_destructInstance -> clearDeallocating -> ClearDeallocating_slow Find clearDeallocating_slow for analysis

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

If there are weak reference tables, weak_clear_NO_LOCK is further called to clear the weak reference tables

void weak_clear_no_lock(weak_table_t *weak_table, Objc_object *referent = (objc_object *)referent_id; Weak_entry_t *entry = Weak_entry_for_referent (weak_table, referent); if (entry == nil) { /// XXX shouldn't happen, but does with mismatched CF/objc //printf("XXX no entry for clear deallocating %p\n", referent); 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; } else if (*referrer) { _objc_inform("__weak variable at %p holds %p instead of %p. " "This is probably incorrect use of  " "objc_storeWeak() and objc_loadWeak(). " "Break on objc_weak_error to debug.\n", referrer, (void*)*referrer, (void*)referent); objc_weak_error(); Weak_entry_remove (weak_table, entry); weak_entry_remove(weak_table, entry); }Copy the code

Internally, weak_entry_for_referent will be called according to the address value of the object as key, and bitwise and operation will be performed with mask to find the corresponding weak reference table in the hash table

static weak_entry_t * weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent) { ASSERT(referent); weak_entry_t *weak_entries = weak_table->weak_entries; if (! weak_entries) return nil; Weak_table ->mask; // Use address value (as key) &mask = index size_t begin = hash_pointer(referent) &weak_table ->mask; size_t index = begin; size_t hash_displacement = 0; while (weak_table->weak_entries[index].referent ! = referent) { index = (index+1) & weak_table->mask; if (index == begin) bad_weak_table(weak_table->weak_entries); hash_displacement++; if (hash_displacement > weak_table->max_hash_displacement) { return nil; } } return &weak_table->weak_entries[index]; }Copy the code

Through source code analysis, we can know that weak modified attributes will exist in a Weak_table type hash table, and then take the address value of the current object as key to store all weak reference tables; When the object is freed, the same steps are used to find the weak reference table from the hash weak_table and remove it

Conclusion:

  • Maintain one file globallySideTables table
  • SideTablesContains more than oneSideTableCan be found through the hash algorithm of the object addressSideTable
  • SideTableCorresponds to multiple objects that storeReference counter tableandA weak reference table, you need to hash the object again to find itReference counter tableandA weak reference table

The relationship of SideTable is shown below

autorelease

We add an autoRelease to an object in the MRC environment, and the object is automatically managed by reference counting when it is put into the auto-release pool

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

So what does AutoRelease do?

The implementation principle of @autoreleasepool

Let’s take a look at how @Autoreleasepool works

Let’s take a look at this by converting it to C++ code

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
    }
    return 0;
}
Copy the code

Discovery generates a structure of type __AtAutoreleasePool with constructors and destructors generated inside

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

@AutoReleasepool is implemented by calling objc_autoreleasePoolPush and objc_autoreleasePoolPop at the beginning and end of the code block, respectively

Then we can find the corresponding implementation in objC4 source nsobject.mm

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

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

We find that both functions call the AutoreleasePoolPage type. We can see that this type is defined as follows and is essentially a structure of type AutoreleasePoolPageData

class AutoreleasePoolPage : private AutoreleasePoolPageData { friend struct thread_data_t; Static size_t const SIZE = #if PROTECT_AUTORELEASEPOOL PAGE_MAX_SIZE; static size_t const SIZE = #if PROTECT_AUTORELEASEPOOL PAGE_MAX_SIZE; // must be multiple of vm page size #else PAGE_MIN_SIZE; // size and alignment, power of 2 #endif private: static pthread_key_t const key = AUTORELEASE_POOL_KEY; static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing static size_t const COUNT = SIZE / sizeof(id); static size_t const MAX_FAULTS = 2; . } // AutoreleasePoolPageData struct AutoreleasePoolPageData { #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS struct AutoreleasePoolEntry { uintptr_t ptr: 48; uintptr_t count: 16; static const uintptr_t maxCount = 65535; // 2^ 16-1}; static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!" ); #endif magic_t const magic; __unsafe_unretained id *next; pthread_t const thread; AutoreleasePoolPage * const parent; // Pointer to the last AutoreleasePoolPage (the first in the list is nil) AutoreleasePoolPage *child; // Uint32_t const depth; uint32_t hiwat; AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat) : magic(), next(_next), thread(_thread), parent(_parent), child(nil), depth(_depth), hiwat(_hiwat) { } };Copy the code
conclusion
  • @autoreleasepool

An __AtAutoreleasePool object is generated underneath

  • __AtAutoreleasePoolAnd then two functions are generated internallyobjc_autoreleasePoolPushandobjc_autoreleasePoolPop, respectively, at the beginning and end of the braces scopePush (push)andPop (out of stack)operation
  • __AtAutoreleasePoolThe bottom is to rely onAutoreleasePoolPage objectTo operate
  • AutoreleasePoolPageIs a bidirectional linked list structure, its internalchildIt points to the next oneAutoreleasePoolPage object;parentIt’s going to point to the last oneAutoreleasePoolPage object

Objc_autoreleasePoolPush source code analysis

We by calling the trajectory objc_autoreleasePoolPush – > AutoreleasePoolPage: : push to analyze internal specific do

Static inline void *push() {id *dest; If (slowPath (DebugPoolAllocation)) {// Each autoRelease pool starts on a new pool page. Add POOL_BOUNDARY to dest = autoreleaseNewPage(POOL_BOUNDARY); } else {// Add POOL_BOUNDARY dest = autoreleaseFast(POOL_BOUNDARY); } ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY); return dest; }Copy the code

** If there is no new Page object, autoreleaseNewPage is called

Static __attribute__((noinline)) id *autoreleaseNewPage(id obj) {AutoreleasePoolPage *page = hotPage(); // Add POOL_BOUNDARY to page if (page) return autoreleaseFullPage(obj, page); else return autoreleaseNoPage(obj); } static inline AutoreleasePoolPage *hotPage() {AutoreleasePoolPage *result = (AutoreleasePoolPage *) tls_get_direct(key); If ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil; if (result) result->fastcheck(); return result; }Copy the code

AutoreleaseNewPage internally determines whether an operation has a page or not

1. If there is a page, call autoreleaseFullPage to push the object onto the stack

static __attribute__((noinline)) id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) { // The hot page is full. // Step to the next non-full page, adding a new page if necessary. // Then add the object to that page. ASSERT(page == hotPage()); ASSERT(page->full() || DebugPoolAllocation); If (page->child) page = page->child; if (page->child) page = page->child; Else Page = new AutoreleasePoolPage(page); else page = new AutoreleasePoolPage(page); } while (page->full()); // Set to the current operating page setHotPage(page); Return page->add(obj); } static inline void setHotPage(AutoreleasePoolPage *page) {if (page) page-> fastCheck (); tls_set_direct(key, (void *)page); } static inline AutoreleasePoolPage *coldPage() { AutoreleasePoolPage *result = hotPage(); if (result) { while (result->parent) { result = result->parent; result->fastcheck(); } } return result; }Copy the code

Do the actual pushdown in add

id *add(id obj) { ASSERT(! full()); unprotect(); id *ret; #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS if (! DisableAutoreleaseCoalescing || ! DisableAutoreleaseCoalescingLRU) { if (! DisableAutoreleaseCoalescingLRU) { if (! empty() && (obj ! = POOL_BOUNDARY)) { AutoreleasePoolEntry *topEntry = (AutoreleasePoolEntry *)next - 1; for (uintptr_t offset = 0; offset < 4; offset++) { AutoreleasePoolEntry *offsetEntry = topEntry - offset; if (offsetEntry <= (AutoreleasePoolEntry*)begin() || *(id *)offsetEntry == POOL_BOUNDARY) { break; } if (offsetEntry->ptr == (uintptr_t)obj && offsetEntry->count < AutoreleasePoolEntry::maxCount) { if (offset > 0) { AutoreleasePoolEntry found = *offsetEntry; memmove(offsetEntry, offsetEntry + 1, offset * sizeof(*offsetEntry)); *topEntry = found; } topEntry->count++; ret = (id *)topEntry; // need to reset ret goto done; } } } } else { if (! empty() && (obj ! = POOL_BOUNDARY)) { AutoreleasePoolEntry *prevEntry = (AutoreleasePoolEntry *)next - 1; if (prevEntry->ptr == (uintptr_t)obj && prevEntry->count < AutoreleasePoolEntry::maxCount) { prevEntry->count++; ret = (id *)prevEntry; // need to reset ret goto done; }}}} #endif // Pass the object store location ret = next; // Next ++ = next; // better than 'return next-1' because of aliasing // next++ = next; #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS // Make sure obj fits in the bits available for it ASSERT(((AutoreleasePoolEntry *)ret)->ptr == (uintptr_t)obj); #endif done: protect(); return ret; }Copy the code

2. In autoreleaseNewPage, if there is no page, call autoreleaseNoPage to create a new page, and then press the stack

static __attribute__((noinline)) id *autoreleaseNoPage(id obj) { // "No page" could mean no pool has been pushed // or an empty placeholder pool has been pushed and has no contents yet ASSERT(! hotPage()); bool pushExtraBoundary = false; // Determine if it is an empty placeholder, if so, // We are pushing a second pool over the empty placeholder pool pushing the first object into the empty placeholder pool. // Before doing that, push a pool boundary on behalf of the pool // that is currently represented by the empty placeholder. pushExtraBoundary = true; } // If POOL_BOUNDARY is not a pool, else if (obj! = POOL_BOUNDARY && DebugMissingPools) { // We are pushing an object with no pool in place, // and no-pool debugging was requested by environment. _objc_inform("MISSING POOLS: (%p) Object %p of class %s " "autoreleased with no pool in place - " "just leaking - break on " "objc_autoreleaseNoPool() to debug", objc_thread_self(), (void*)obj, object_getClassName(obj)); objc_autoreleaseNoPool(obj); return nil; Else if (obj == POOL_BOUNDARY &&!) if (obj == POOL_BOUNDARY &&! DebugPoolAllocation) { // We are pushing a pool with no pool in place, // and alloc-per-pool debugging was not requested. // Install and return the empty pool placeholder. return setEmptyPoolPlaceholder(); } // We are pushing an object or a non-placeholder'd pool. Install the first page *page = new AutoreleasePoolPage(nil); // Set the current page to setHotPage(page); // void void void void void void void void void void void void void void void If (pushExtraBoundary) {page->add(POOL_BOUNDARY); } // Push the requested object or pool. return page->add(obj); }Copy the code

Step 2: If you already have a Page, go directly to autoreleaseFast and judge separately

static inline id *autoreleaseFast(id obj) { AutoreleasePoolPage *page = hotPage(); if (page && ! Page ->full()) {return page->add(obj); Return autoreleaseFullPage(obj, page);} else if (page) {return autoreleaseFullPage(obj, page); } else {// page does not exist, new return autoreleaseNoPage(obj); }}Copy the code
conclusion
  • eachAutoreleasePoolPage objectThere will be a certain amount of storage space, roughly occupied4096 bytes
  • eachAutoreleasePoolPage objectInternal member variables will occupy56 bytesAnd then the remaining space is used for storageAutorelease objects
  • each@autoreleasePoolThe beginning will be firstPOOL_BOUNDARY objectPush it onto the stack before it starts to storeAutorelease objectsAnd,Push methodReturns thePOOL_BOUNDARY objectMemory address of
  • When aAutoreleasePoolPage objectWhen it’s full, it moves on to the next oneAutoreleasePoolPage objectStart storing in
  • AutoreleasePoolPage objectThe inside of thebeginandendCorresponding toAutorelease objectsStart address and end address for starting the stack
  • AutoreleasePoolPage objectThe inside of thenextPoint to the next one you can storeautoreleaseObject address area

The analysis of the entire push process above can be summarized in the following figure

Autorelease source code analysis

So let’s see what’s going on underneath autoRelease

// objc_object::autorelease inline id objc_object::autorelease() { ASSERT(! isTaggedPointer()); if (fastpath(! ISA()->hasCustomRR())) { return rootAutorelease(); } return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease)); } // objc_object::rootAutorelease Inline ID objc_object::rootAutorelease() {// Return if if it is TaggedPointer (isTaggedPointer()) return (id)this; if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this; return rootAutorelease2(); } // objc_object::rootAutorelease2 __attribute__((noinline,used)) id objc_object::rootAutorelease2() { ASSERT(! isTaggedPointer()); return AutoreleasePoolPage::autorelease((id)this); }Copy the code

You still end up calling autoRelease of AutoreleasePoolPage

static inline id autorelease(id obj) { ASSERT(! obj->isTaggedPointerOrNil()); id *dest __unused = autoreleaseFast(obj); #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS ASSERT(! dest || dest == EMPTY_POOL_PLACEHOLDER || (id)((AutoreleasePoolEntry *)dest)->ptr == obj); #else ASSERT(! dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj); #endif return obj; }Copy the code

Then go to autoreleaseFast for quick pushdown. Autoreleasepool will only push objects that have called AutoRelease

The overall analysis of autoRelease and objc_autoreleasePush is shown below

Objc_autoreleasePoolPop source code analysis

What is done internally by calling objc_autoreleasePoolPop -> AutoreleasePoolPage::pop

static inline void pop(void *token) { AutoreleasePoolPage *page; id *stop; If (token == (void*)EMPTY_POOL_PLACEHOLDER) {// Put the value at the end of the table before you put the value at the end of the table hotPage(); if (! // Pool was never used. Clear the placeholder. // If the current page does not exist, return setHotPage(nil); } // Pool pages remain remain of allocated tokens for re-use as usual. // If the current page exists, Then set the current page to coldPage and the token to the start of coldPage = coldPage(); token = page->begin(); } else {// Get token where page = pageForPointer(token); } stop = (id *)token; // Determine whether the last position is POOL_BOUNDARY if (*stop! If (stop == page->begin() &&! page->parent) { // Start of coldest page may correctly not be POOL_BOUNDARY: // 1. Top-level pool is popped, leaving the cold page in place // 2. An object is autoreleased with no pool // And there is no parent node, } else {// Error. For bincompat purposes this is not // fatal in executables built with old SDKs. Return badPop(token); } } if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) { return popPageDebug(token, page, stop); } // return popPage<false>(token, page, stop); }Copy the code

Begin and end correspond to the start and end addresses of the AutoRelease object, respectively

Id * begin() {return (id *) ((uint8_t *)this+sizeof(*this)); } // Start address: start address + PAGE_MAX_SIZE id * end() {return (id *) ((uint8_t *)this+SIZE); } // coldPage static inline AutoreleasePoolPage *coldPage() { AutoreleasePoolPage *result = hotPage(); if (result) { while (result->parent) { result = result->parent; result->fastcheck(); } } return result; }Copy the code

[Step 2] Then enter the popPage to remove the stack operation

template<bool allowDebug> static void popPage(void *token, AutoreleasePoolPage *page, id *stop) { if (allowDebug && PrintPoolHiwat) printHiwat(); Page ->releaseUntil(stop); If (allowDebug && DebugPoolAllocation && page->empty()) {// Special case: Delete everything during page-per-pool debugging // Get the parent node of the current page AutoreleasePoolPage *parent = page->parent; // Delete the current page page->kill(); // set the operation page to the parent node page setHotPage(parent); } else if (allowDebug && DebugMissingPools && page->empty() && ! page->parent) { // special case: delete everything for pop(top) // when debugging missing autorelease pools page->kill(); setHotPage(nil); } else if (page->child) { // hysteresis: Keep one empty child if the page is more than half full If (page->lessThanHalfFull()) {page->child->kill(); } else if (page->child->child) { page->child->child->kill(); } } } // kill void kill() { // Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbage AutoreleasePoolPage *page = this; while (page->child) page = page->child; AutoreleasePoolPage *deathptr; do { deathptr = page; // The child node becomes the parent node page = page->parent; if (page) { page->unprotect(); Page ->child = nil; page->protect(); } delete deathptr; } while (deathptr ! = this); }Copy the code

A releaseUntil loop is called internally to iterate over the POP operation

void releaseUntil(id *stop) { // Not recursive: We don't want to blow out the stack if a thread accumulates a stupendous amount of garbage Determine if the next object is equal to stop, and if not, enter the while loop while (this->next! = stop) { // Restart from hotPage() every time, in case -release // autoreleased more objects AutoreleasePoolPage *page = hotPage(); // fixme I think this `while` can be `if`, // If the current page is empty while (page->empty()) {// Assign page to parent page = page->parent; // Set the current page to the parent page setHotPage(page); } page->unprotect(); #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS AutoreleasePoolEntry* entry = (AutoreleasePoolEntry*) --page->next; // create an obj with the zeroed out top byte and release that id obj = (id)entry->ptr; int count = (int)entry->count; // grab these before memset #else id obj = *--page->next; #endif memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); page->protect(); if (obj ! = POOL_BOUNDARY) {// If not POOL_BOUNDARY, Release #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS release count+1 times since it is count of the Additional // autoreleases beyond the first one for (int i = 0; i < count + 1; i++) { objc_release(obj); } #else objc_release(obj); #endif}} // Set the current page setHotPage(this); #if DEBUG // we expect any children to be completely empty for (AutoreleasePoolPage *page = child; page; page = page->child) { ASSERT(page->empty()); } #endif }Copy the code
conclusion
  • popFunction willPOOL_BOUNDARYThe memory address of the
  • Autorelease objectsfromEnd Indicates the end addressStart sendingNews releaseFind it all the wayPOOL_BOUNDARYSo far,
  • Once the current page is found empty, it goes to the previous pagepopAnd free the current page
  • The whole order of loading and unloading is advanced and then out, which is the same as the order in the stack, but it does not mean that what is said here is the real stack

The above analysis of the entire pop off stack process can be summarized in the following figure

Analyze the execution process by printing

We can print and analyze the entire Autorelease process using a private function _objc_autoreleasePoolPrint

Extern void _objc_autoreleasePoolPrint(void); 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() MJPerson *p3 = [[[Person alloc] init] autorelease]; _objc_autoreleasePoolPrint(); } // pop(r2) } // pop(r1) return 0; }Copy the code

You can see the print as follows

objc[25057]: ############## objc[25057]: AUTORELEASE POOLS for thread 0x1000e7e00 objc[25057]: 5 releases pending. objc[25057]: [0x107009000] ................ PAGE (hot) (cold) objc[25057]: [0x107009038] ################ POOL 0x107009038 objc[25057]: [0x107009040] 0x10060f120 Person objc[25057]: [0x107009048] 0x100606800 Person objc[25057]: [0x107009050] ################ POOL 0x107009050 objc[25057]: [0x107009058] 0x100607de0 Person objc[25057]: # # # # # # # # # # # # # #Copy the code

The interview questions

What does 1.@dynamic and @synthesize mean

In older compilers, the @synthesize generated underlined member variables and setter and getter implementations. Today’s compilers do it automatically without this keyword

// Synthesize age = _age; // Without assigning, the member variable is age@synthesize ageCopy the code

Adding at sign dynamic does not automatically generate the implementation and member variables of the setter and getter

@dynamic age;
Copy the code

All declarations are determined by @property

2. Run the following two pieces of code separately and think about what can happen. What’s the difference

@interface ViewController () @property (strong, nonatomic) NSString *name; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; dispatch_queue_t queue = dispatch_get_global_queue(0, 0); For (int I = 0; i < 1000; i++) { dispatch_async(queue, ^{ self.name = [NSString stringWithFormat:@"abcdefghijk"]; }); } for (int I = 0; i < 1000; i++) { dispatch_async(queue, ^{ self.name = [NSString stringWithFormat:@"abc"]; }); } } @endCopy the code

[First code]

Since assigning self.name calls the setter for name, the implementation of the setter releases the old member variable and then assigns the new member variable; Because it is called concurrently by multiple threads, the name is released multiple times causing bad memory access

Solution: Assign self.name to lock in the dispatch_async callback

[Second code]

The program won’t crash.

The second string is optimized by TaggedPointer from the print type and memory address, so the setter will not be called and will not be released multiple times causing a crash

NSString *str1 = [NSString stringWithFormat:@"abcdefghijk"]; NSString *str2 = [NSString stringWithFormat:@"abc"]; NSLog(@"%@ %@", [str1 class], [str2 class]); NSLog(@"%p %p", str1, str2); // Output: __NSCFString NSTaggedPointerString // 0x600000A8D6C0 0x818FF819168B363DCopy the code

3. What has ARC done for us

ARC is a collaboration between LLVM and Runtime; LLVM generates the memory management code at compile time, and Runtime does the memory management at Runtime

4. When was the local variable released

  • If it is an undecorated local variable, it is released at the end of the scope within the function
  • If it is be@autoreleasePoolModified, then it will be managed by the automatic release pool
  • If it is calledautorelease, then will be added toRunLoopFor management in

In the code below, the object is released after viewWillAppear is executed

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *person = [[[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

Runloop will execute an objc_autoreleasePoolPush once when it enters the loop, then an objc_autoreleasePoolPop and objc_autoreleasePoolPush once before it goes to sleep, and so on. When the program actually exits, execute objc_autoreleasePoolPop again

You can also see that viewDidLoad and viewWillAppear are in the same run loop