When it comes to App optimization, optimizing memory will inevitably be mentioned, so this chapter will explore memory management.

One: Memory layout

With respect to memory partitioning, explore the basics of multithreading, which has already been covered, and add a few additions. In addition to the usual five areas, there are also the core area and the reserve area.

Kernel area: The area of the kernel given to the system for processing.

Reserved area: an area reserved for the system to perform some operations

Tip: The stack area will grow down and the heap area will grow up

Two: memory management scheme

Tagged Pointer

Overview and Examples

In September 2013, apple launched iPhone5s, which was equipped with A7 dual-core processor with 64-bit architecture for the first time. In order to save memory and improve execution efficiency, apple proposed the concept of Tagged Pointer.

Before using Tagged Pointer, if you declare an NSNumber *number = @10; Variable, you need an 8-byte pointer variable number, and a 16-byte NSNumber object, which points to the address of the NSNumber object. This takes 24 bytes of memory.

After using Tagged Pointer, the Data stored in the NSNumber Pointer becomes Tag + Data, which means the Data is stored directly in the Pointer. The data 10 is stored directly in the pointer variable number, which takes only 8 bytes.

But when Pointers are insufficient to store data, dynamic memory allocation is used to store data.

So let’s look at the code

- (void)taggedPointerDemo1 { self.queue = dispatch_queue_create("com.wj.cn", DISPATCH_QUEUE_CONCURRENT); for (int i = 0; i<10000; i++) { dispatch_async(self.queue, ^{ self.nameStr = [NSString stringWithFormat:@"wj"]; NSLog(@"%@",self.nameStr); }); } } - (void)taggedPointerDemo2 { self.queue = dispatch_queue_create("com.wj.cn", DISPATCH_QUEUE_CONCURRENT); for (int i = 0; i<10000; I++) {dispatch_async(self.queue, ^{self.nameStr = [NSString stringWithFormat:@"wj_ 15 "]; NSLog(@"%@",self.nameStr); }); }}Copy the code

The code in taggedPointerDemo1 and taggedPointerDemo2 is almost the same, but the actual results are different: taggedPointerDemo1 works fine, while taggedPointerDemo2 crashes.

  • Actually the crash cause is very simple, when calledself.nameStrWhen assigning, it actually gets called quite frequentlysetter, therefore,retainandreleaseCan also be called frequently, because multithreading causes the same object to be released multiple times at a time, resulting in excessive release and crash.

Why does taggedPointerDemo1 work? Let’s go through the breakpoint!

The object type of taggedPointerDemo1 is NSTaggedPointerString. The object type of taggedPointerDemo2 is NSCFString

The retain and release methods are defined as follows in objC source code:

__attribute__((aligned(16), flatten, noinline))
id 
objc_retain(id obj)
{
    if (obj->isTaggedPointerOrNil()) return obj;
    return obj->retain();
}

__attribute__((aligned(16), flatten, noinline))
void 
objc_release(id obj)
{
    if (obj->isTaggedPointerOrNil()) return;
    return obj->release();
}
Copy the code

The system decides that if the object type is isTaggedPointer it returns and does nothing else, so the object in taggedPointerDemo1 is already optimized and does not call the setter at all

The source code interpretation

In as he is not on read_image, actually contains the Tagged Pointer handling initializeTaggedPointerObfuscator ()

static void initializeTaggedPointerObfuscator(void) { if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) || // Set the obfuscator to zero for apps linked against older SDKs, // in case they're relying on the tagged pointer representation. DisableTaggedPointerObfuscation) { objc_debug_taggedpointer_obfuscator = 0; } else { // Pull random data into the variable, then shift away all non-payload bits. arc4random_buf(&objc_debug_taggedpointer_obfuscator, sizeof(objc_debug_taggedpointer_obfuscator)); objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK; }}Copy the code

The underlying objC_debug_taggedpointer_obfuscator is processed by two xOR codecs.

extern uintptr_t objc_debug_taggedpointer_obfuscator;

static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
    return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}

static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;

}
Copy the code

For example, look at the print result

- (void)viewDidLoad {

    [super viewDidLoad];

    NSString *str1 = [NSString stringWithFormat:@"a"];

    NSString *str2 = [NSString stringWithFormat:@"b"];

    NSLog(@"%p-%@",str1,str1);

    NSLog(@"%p-%@",str2,str2);

    // NSLog(@"0x%lx",_objc_decodeTaggedPointer_(str2));
    
}

uintptr_t
_objc_decodeTaggedPointer_(id ptr)
{
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
Copy the code

The printed result is actually not the real pointer address, this address is after confusion, we need_objc_decodeTaggedPointerDecode to get the real address. Open the comment section of the code above to get the real pointer address.

_objc_makeTaggedPointer

So in _objc_makeTaggedPointer we’re going to pass in a flag bit tag and we’re going to do different bit operations and then we’re going to render taggedPointer in flag bit + value + value type format.

The following are the Tagged Pointer flags defined by the system.

enum objc_tag_index_t : uint16_t
{
    // 60-bit payloads
    OBJC_TAG_NSAtom            = 0, 
    OBJC_TAG_1                 = 1, 
    OBJC_TAG_NSString          = 2, 
    OBJC_TAG_NSNumber          = 3, 
    OBJC_TAG_NSIndexPath       = 4, 
    OBJC_TAG_NSManagedObjectID = 5, 
    OBJC_TAG_NSDate            = 6,

    // 60-bit reserved
    OBJC_TAG_RESERVED_7        = 7, 

    // 52-bit payloads
    OBJC_TAG_Photos_1          = 8,
    OBJC_TAG_Photos_2          = 9,
    OBJC_TAG_Photos_3          = 10,
    OBJC_TAG_Photos_4          = 11,
    OBJC_TAG_XPC_1             = 12,
    OBJC_TAG_XPC_2             = 13,
    OBJC_TAG_XPC_3             = 14,
    OBJC_TAG_XPC_4             = 15,

    OBJC_TAG_First60BitPayload = 0, 
    OBJC_TAG_Last60BitPayload  = 6, 
    OBJC_TAG_First52BitPayload = 8, 
    OBJC_TAG_Last52BitPayload  = 263, 

    OBJC_TAG_RESERVED_264      = 264
};
Copy the code

conclusion

  • Starting from 64-bit, iOS introduces Tagged Pointer technology to optimize small object storage 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, which means that the Data is stored directly in the Pointer. The Tagged Pointer is no longer an address, but a real value. So, it’s not really an object anymore, it’s just a normal variable in an object’s skin. Therefore, its memory is not stored in the heap and does not require malloc and free.

  • Three times more efficient at memory reads and 106 times faster at creation. It not only reduces the memory footprint of programs on 64-bit machines, but also improves the running efficiency. It perfectly solves the problems of storage and access efficiency of small memory objects.

  • This is a special pointer that does not point to any address

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

NONPOINTER_ISA

Nonpointer_isa, which was introduced in the nature of isa in the object principle, is also one of the memory management solutions. Isa is an 8-byte (64-bit) pointer, and it is wasteful to use only isa points, so ISA is mixed with other data to save memory.

SideTable

Let’s look at the SideTable structure

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

Slock: unlock refcnts: reference count table Weak_table: weak reference table

Globally, hash tables are SideTables

static StripedMap<SideTable>& SideTables() {
    return SideTablesMap.get();
}
Copy the code

Why not put it on a list?

  • Low security – If all objects are in a hash table, unlock an object when it needs to be processed, and other objects in the table cannot be secured.
  • Optimize locking and unlocking speed – For hash tables with high operation frequency, splitting into multiple tables can improve performance.

Three: MRC & ARC

alloc

Object principle of alloc & init exploration

retain

  • Obj ->retain() obj->retain()

  • Objc_object ::retain calls rootRetain() with a high probability of fastPath, and with a low probability of message send calls externally supplied SEL_retain

  • RootRetain calls rootRetain(false, false)

objc_object::rootRetain(bool tryRetain, bool handleOverflow) { if (isTaggedPointer()) return (id)this; bool sideTableLocked = false; bool transcribeToSideTable = false; isa_t oldisa; isa_t newisa; do { transcribeToSideTable = false; oldisa = LoadExclusive(&isa.bits); newisa = oldisa; 🌹 check if nonpointer if (slowpath(! newisa.nonpointer)) { ClearExclusive(&isa.bits); if (rawISA()->isMetaClass()) return (id)this; if (! tryRetain && sideTableLocked) sidetable_unlock(); if (tryRetain) return sidetable_tryRetain() ? (id)this : nil; else return sidetable_retain(); } // don't check newisa.fast_rr; Slowpath (tryRetain && newisa.deallocating) {we already called any RR overrides 🌹 ClearExclusive(&isa.bits); if (! tryRetain && sideTableLocked) sidetable_unlock(); return nil; } uintptr_t carry; 🌹 operates on extra_rc to handle reference counts newISa.bits = addC (newisa.bits, RC_ONE, 0, &carry); // extra_rc++ if (slowpath(carry)) {🌹 overload, operate hash handleOverflow) { ClearExclusive(&isa.bits); return rootRetain_overflow(tryRetain); } // Leave half of the retain counts inline and // prepare to copy the other half to the side table. if (! tryRetain && ! sideTableLocked) sidetable_lock(); sideTableLocked = true; transcribeToSideTable = true; newisa.extra_rc = RC_HALF; newisa.has_sidetable_rc = true; } } while (slowpath(! StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))); If (slowpath(transcribeToSideTable)) {// Copy the other half of the retain counts to the side table. 🌹 sidetable_addExtraRC_nolock(RC_HALF); } if (slowpath(! tryRetain && sideTableLocked)) sidetable_unlock(); return (id)this; }Copy the code

RootRetain’s internal implementation is actually a do-while loop:

  • Check whether it is nonPOinter_ISA (low probability event). If it is not nonPOinter_ISA (low probability event), process the reference technical table in the hash table sideTable.

    • Find the corresponding hash table to proceed+=SIDE_TABLE_RC_ONE, includingSIDE_TABLE_RC_ONEMove two places left to find the reference counter table
  • Normally for nonPOinter_ISA, addC is called to handle the reference count and carry is used to record whether the reference count is overloaded

    • rightisaThe 45th position (RC_ONEinarm64In 45)extra_rcOperation processing
  • Overloaded, store half of the extra_RC reference count in the reference count table and mark ISA -> has_SIDETABLE_rc as true

    • The reason why reference count storage using ISA is preferred is that the performance of ISA operations is high. Operation reference count tables need to be unlocked.

If the EXTRA_RC is not freed, then half of the full state is stored in extra_RC and the other half in the hash table.

release

Release and retain are very similar.

  • Obj ->release(); / / obj->release()

  • Objc_object ::release calls rootRelease() with a high probability of fastPath and SEL_release with a low probability of message sending

  • RootRelease calls rootRelease(true, false)

  • RootRelease’s internal implementation also has a do-while loop

    • Check whether it is nonPOinter_ISA (low probability event). If it is not nonPOinter_ISA, the technical table referenced in the hash table is processed

    • Normally for nonPOinter_ISA the subc function is called to handle the reference count and carry is used to record if the reference count is overloaded

    • Overloading leads to the Underflow branch

      • ifisaIn thehas_sidetable_rcStart handling reference counting when it is true, otherwiseisaIn thedeallocatingMark true for release
      • ifisaIn theextra_rcHalfway down, it clears out the values stored in the reference count table and puts them backextra_rcIn the backretrythedo-whileCycle.

retainCount

Look at the code below

    NSObject *objc = [[NSObject alloc]init];
    NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)objc));
Copy the code

It prints a 1,init is just a constructor, it doesn’t do reference counting, alloc we looked at, we didn’t see the code, so how does that 1 come up?

RetainCount eventually calls rootRetainCount

inline uintptr_t objc_object::rootRetainCount() { if (isTaggedPointer()) return (uintptr_t)this; sidetable_lock(); isa_t bits = LoadExclusive(&isa.bits); ClearExclusive(&isa.bits); 1 uintptr_t rc = 1 + bits.extra_rc; 2 uintptr_t rc = 1 + bits.extra_rc; if (bits.has_sidetable_rc) { rc += sidetable_getExtraRC_nolock(); } sidetable_unlock(); return rc; } sidetable_unlock(); return sidetable_retainCount(); }Copy the code
  • Check whether it is isTaggedPointer

  • If it is nonpointer, the current reference count =1+extrac_rc

    • This line of code will tell you thatallocThe resulting object reference count is0In order not to give developers a reference count of0Destroy the illusion created by default plus one
  • Then check whether has_sidetable_rc has additional hash tables

    • If there is a reference count plus the number in the reference count table
  • So reference count =1 + Extrac_rc + sidetable_getExtraRC_nolock

autorelease

Autorelease adds objects to the automatic release pool.

Autorelease will eventually call rootAutorelease2 -> AutoRelease () -> autoreleaseFast()

AutoreleaseFast is described below

dealloc

  • Start by calling _objc_rootDealloc.

  • _objc_rootDealloc call rootDealloc

  • rootDealloc

    • Determine whetherisTaggedPointerIf it is, go straight back. If it is not, continue down
    • Determine if there are weak references, associated objects, c++ destructors, extra hash tables in the isa identifier bits, and call them if there areobject_dispose, otherwise directlyfree
inline void objc_object::rootDealloc() { if (isTaggedPointer()) return; // fixme necessary? if (fastpath(isa.nonpointer && ! isa.weakly_referenced && ! isa.has_assoc && ! isa.has_cxx_dtor && ! isa.has_sidetable_rc)) { assert(! sidetable_present()); free(this); } else { object_dispose((id)this); }}Copy the code

In the object_dispose

  • First empty processing
  • Then callobjc_destructInstance(Core part)
  • Finally,freeRelease object
id object_dispose(id obj) { if (! obj) return nil; objc_destructInstance(obj); free(obj); return nil; }Copy the code

In the objc_destructInstance

  • Determine if there isC++ destructorandassociationsIf yes, call it separatelyobject_cxxDestruct,_object_remove_assocationsFor processing
  • And then callclearDeallocating
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);
        obj->clearDeallocating();
    }
    return obj;
}
Copy the code

In the clearDeallocating

  • Determine whether or notnonpointer, is calledsidetable_clearDeallocatingClear hash table
  • Determine if there isA weak referenceandAdditional reference counting table has_SIDETABLE_rc, is calledclearDeallocating_slowWeak reference table and reference count table processing
inline void objc_object::clearDeallocating() { 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

ClearDeallocating_slow handles the weak reference table

objc_object::clearDeallocating_slow()

{

    ASSERT(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));
    SideTable& table = SideTables()[this];
    table.lock();
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}
Copy the code

Complete flow chart:

Four: strong and weak reference

Weak principle

The implementation principle of the underlying exploration – weak has been explored before

Circular references in NSTimer

static int num = 0; @interface ViewController () @property (nonatomic, strong) NSTimer *timer; @ end @ implementation ViewController - (void) didMoveToParentViewController: (UIViewController *) parent {/ / no matter or push came in If (parent == nil) {[self.timer invalidate]; self.timer = nil; NSLog (@ "timer go"); } } - (void)viewDidLoad { [super viewDidLoad]; self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(fire) userInfo:nil repeats:YES]; } - (void)fire { num++; NSLog(@"current - %d",num); } - (void)dealloc { [self.timer invalidate]; self.timer = nil; NSLog(@"%s", __FUNCTION__); }Copy the code

The above code is certainly will cause a circular reference, we look at the official document, the target of scheduledTimerWithTimeInterval: the self, the API will launch a strong reference to incoming target objects.

WeakSelf = self can we use __weak Typeof (self) weakSelf = self to solve the circular reference like Block?

The answer is no

In the console, self and weakSelf are both 0x12AF05570, that is, they point to the same object, but we print the address of self and weakSelf Pointers, they are not the same, that is to say, self and weakSelf are two completely different Pointers, but pointing to the same object. The official document also says that NSTimer will strongly reference the object passed in, so passing in weakSelf cannot solve the circular reference.

In the Block, what we hold is the pointer address. In order to solve the circular reference, we usually pass in a weakSelf and get a weakSelf in the Block instead of a real object.

Circular reference resolution

Scheme 1: Create a Timer in Block format

The simplest solution

self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"timer fire - %@",timer);
    }];
Copy the code

Option 2: Invalidate in advance

  • Since dealloc cannot come, resolve this strong reference before the dealloc function is called

  • NSTimer can be handled in viewWillDisappear, viewDidDisappear, but this does not work well, because skip to next timer will also stop working, inconsistent with the business

  • Use didMoveToParentViewController is a good way to solve this strong reference.

The complete code

static int num = 0; @interface ViewController () @property (nonatomic, strong) NSTimer *timer; @ end @ implementation ViewController - (void) didMoveToParentViewController: (UIViewController *) parent {/ / no matter or push came in If (parent == nil) {[self.timer invalidate]; self.timer = nil; NSLog (@ "timer go"); } } - (void)viewDidLoad { [super viewDidLoad]; self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(fire) userInfo:nil repeats:YES]; } - (void)fire { num++; NSLog(@"current - %d",num); }Copy the code

Option 3: The mediator model

The Timer is instantiated using an intermediary, and the selector needs to be performed by determining whether the target exists

#import "LGTimerWapper.h" #import <objc/message.h> @interface LGTimerWapper() @property (nonatomic, weak) id target; @property (nonatomic, assign) SEL aSelector; @property (nonatomic, strong) NSTimer *timer; @end @implementation LGTimerWapper - (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{ if (self == [super init]) { self.target = aTarget; // vc self.aSelector = aSelector; / / Method, vc release the if ([self. The target respondsToSelector: self. ASelector]) {Method Method = class_getInstanceMethod([self.target class], aSelector); const char *type = method_getTypeEncoding(method); class_addMethod([self class], aSelector, (IMP)fireHomeWapper, type); self.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:aSelector userInfo:userInfo repeats:yesOrNo]; } } return self; // run runloop void fireHomeWapper(LGTimerWapper *warpper){if (warpper.target) {// vc-dealloc void (*lg_msgSend)(void *,SEL, id) = (void *)objc_msgSend; lg_msgSend((__bridge void *)(warpper.target), warpper.aSelector,warpper.timer); }else{ // warpper.target [warpper.timer invalidate]; warpper.timer = nil; } } - (void)lg_invalidate{ [self.timer invalidate]; self.timer = nil; } - (void)dealloc{ NSLog(@"%s",__func__); } @endCopy the code

Solution 4: Use NSProxy

NSProxy is specifically for message forwarding, the same class as NSObject.

@interface MJProxy : NSProxy + (instancetype)proxyWithTarget:(id)target; @property (weak, nonatomic) id target; @implementation MJProxy + (instancetype)proxyWithTarget:(id)target {// NSProxy object does not need to call init, Because it doesn't have init method MJProxy *proxy = [MJProxy alloc]; proxy.target = target; return proxy; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [self.target methodSignatureForSelector:sel]; } - (void)forwardInvocation:(NSInvocation *)invocatio{ [invocation invokeWithTarget:self.target]; } @endCopy the code

VC use

The self. The timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: [MJProxy proxyWithTarget: self] selector:@selector(timerTest) userInfo:nil repeats:YES];Copy the code

Five: automatic release pool

Print a copy of main. CPP to the blank main.m using the clang command.

int main(int argc, const char * argv[]) { @autoreleasepool { } return 0; } struct __AtAutoreleasePool {🌹 constructor __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush(); } 🌹 destructor ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atAutoReleasepoolobj); } void * atautoreleasepoolobj; }; int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; } return 0; }Copy the code

The bottom layer of the auto-release pool is an __AtAutoreleasePool structure with constructors and destructors.

Take a look at objc’s source code for an explanation:

  • Automatic release tankIs a stack node structure, has the characteristics of the stack – advanced after out
  • Automatic release tankA node can be an object (which can be freed) or an objectPOOL_BOUNDARY(Boundary/sentinel object)
  • Automatic release tankIs the data structure ofTwo-way linked list
  • Automatic release tankwithThe TLS/threadIt does matter

The overall structure is as follows:

Both objc_autoreleasePoolPush and objc_autoreleasePoolPop involve a class called AutoreleasePoolPage

AutoreleasePoolPage structure

class AutoreleasePoolPage; struct AutoreleasePoolPageData { magic_t const magic; // 🌹16 bytes __unsafe_unretained id *next; // 🌹8 bytes pthread_t const thread; //8 bytes AutoreleasePoolPage * const parent; // 🌹8 bytes AutoreleasePoolPage *child; // 🌹8 bytes uint32_t const depth; // 🌹4 bytes uint32_t hiwat; // 🌹4 bytes 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
  • Magic is used to verify that the structure of AutoreleasePoolPage is complete

  • Next points to the next location of the newly added Autoreleased object, initialized to begin()

  • The current thread to which thread points

  • Parent refers to the parent node, and the parent value of the first node is nil

  • Child refers to the child node, and the last node has a child value of nil

  • Depth means depth. It starts at 0 and increases by 1

  • Hiwat stands for High Water Mark — the maximum number of stacks

The official notes also mention sentinels. How many sentinels does an auto-release pool contain? There’s only one.

#   define POOL_BOUNDARY nil
Copy the code

The sentinel object is essentially nil, and its purpose is mainly when objc_autoreleasePoolPop is called:

  • According to incomingThe sentry objectFind the addressThe sentry objectPage where.
  • In the current page, will be later thanThe sentry objectAll insertedAutorelese objectSend them all oncereleaseMessage and moveNext pointerGet to the right place.
  • Clean up from the most recently added object, which can span several pages, untilThe sentry objectPage where.

So how many objects can an AutoreleasePoolPage store?

#define PAGE_MIN_SIZE           PAGE_SIZE
#define PAGE_SIZE               I386_PGBYTES
#define I386_PGBYTES            4096
Copy the code

The POOL_BOUNDARY sentry object is pushed to the top of the stack when the AutoreleasePoolPage is initialized. The POOL_BOUNDARY sentry object is pushed to the top of the stack when the autorelease pool is initialized. So the first page can actually store only (4040-8)/8 = 504 objects, and from the second page you can store 505 objects.

Into the stack

objc_autoreleasePoolPush

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

push()

static inline void *push() { id *dest; If (slowpath(DebugPoolAllocation)) {// Each autorelease pool starts on a new pool page autoreleaseNewPage(POOL_BOUNDARY); } else { dest = autoreleaseFast(POOL_BOUNDARY); } ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY); return dest; }Copy the code

autoreleaseNewPage()

static __attribute__((noinline))
    id *autoreleaseNewPage(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page) return autoreleaseFullPage(obj, page);
        else return autoreleaseNoPage(obj);
    }
Copy the code

AutoreleaseNewPage () does its initialization and eventually calls the following function

Find where it actually called, but note that begin() instead of *next should have been passed in

begin()

id * begin() {
        return (id *) ((uint8_t *)this+sizeof(*this));
    }
Copy the code

Let’s do a code run

So what is 56? The reason for this is that the AutoreleasePoolPage object itself contains 56 bytes of properties, so object pushdown starts after 56 bytes.

autoreleaseFast()

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

AutoreleaseFast is divided into three branches :(hotPage can get the current AutoreleasePoolPage)

  • No current page (just created, meaning the pool has not yet been pushed)

    • callautoreleaseNoPageTo create ahotPage
    • callpage->add(obj)Adds the object toAutoreleasePoolPageIn the stack, willnextThe pointer pans and points to the next position
  • HotPage not full (current page not full)

    • callpage->add(obj)Adds the object toAutoreleasePoolPageThe stack
  • Have hotPage and page full (current page full)

    • callautoreleaseFullPageInitialize a new page
    • callpage->add(obj)Adds the object toAutoreleasePoolPageThe stack

autoreleaseFullPage

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);
        do {
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());
        setHotPage(page);
        return page->add(obj);
    }
Copy the code

AutoreleaseFullPage recursively traverses the subpages of the current page, continuing if they exist, and creating a new AutoreleasePoolPage if they don’t and setting them to HotPage.

Out of the stack

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

We need to pass in a CTXT, which is the sentry object atAutoReleasepoolobj, which is the return value of objc_autoreleasePoolPush() in the constructor below.

Struct __AtAutoreleasePool {🌹 constructor __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush(); } 🌹 destructor ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atAutoReleasepoolobj); } void * atautoreleasepoolobj; };Copy the code

AutoreleasePoolPage::pop(CTXT)->pop->popPage will eventually call popPage popPage

  • throughpage->releaseUntil(stop)Through aThe while loopandNext pointerTo iterate over the callobjc_release(obj)Release the object untilNext pointerThe loop stops when you point to the top of the stack
  • And then startpage->kill()To destroy the currentpage,page->child->kill()And so on, finallysetHotPage(nil)

Relationship with RunLoop

  • KCFRunLoopEntry: When the RunLoop is about to enter, an __AtAutoreleasePool structure object is automatically created and the objc_autoreleasePoolPush() function is called.

  • KCFRunLoopBeforeWaiting: An __AtAutoreleasePool object is automatically destroyed when RunLoop is about to sleep, calling objc_autoreleasePoolPop(). Then create a new __AtAutoreleasePool object and call objc_autoreleasePoolPush().

  • KCFRunLoopBeforeExit destroys the last created __AtAutoreleasePool object on exiting RunLoop and calls objc_autoreleasePoolPop().