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 called
self.nameStr
When assigning, it actually gets called quite frequentlysetter
, therefore,retain
andrelease
Can 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_decodeTaggedPointer
Decode 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_ONE
Move two places left to find the reference counter table
- Find the corresponding hash table to proceed
-
Normally for nonPOinter_ISA, addC is called to handle the reference count and carry is used to record whether the reference count is overloaded
- right
isa
The 45th position (RC_ONE
inarm64
In 45)extra_rc
Operation processing
- right
-
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
- if
isa
In thehas_sidetable_rc
Start handling reference counting when it is true, otherwiseisa
In thedeallocating
Mark true for release - if
isa
In theextra_rc
Halfway down, it clears out the values stored in the reference count table and puts them backextra_rc
In the backretry
thedo-while
Cycle.
- if
-
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 that
alloc
The resulting object reference count is0
In order not to give developers a reference count of0
Destroy the illusion created by default plus one
- This line of code will tell you that
-
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 whether
isTaggedPointer
If 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 are
object_dispose
, otherwise directlyfree
- Determine whether
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 call
objc_destructInstance
(Core part) - Finally,
free
Release 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 is
C++ destructor
andassociations
If yes, call it separatelyobject_cxxDestruct
,_object_remove_assocations
For processing - And then call
clearDeallocating
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 not
nonpointer
, is calledsidetable_clearDeallocating
Clear hash table - Determine if there is
A weak reference
andAdditional reference counting table has_SIDETABLE_rc
, is calledclearDeallocating_slow
Weak 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 tank
Is a stack node structure, has the characteristics of the stack – advanced after outAutomatic release tank
A node can be an object (which can be freed) or an objectPOOL_BOUNDARY
(Boundary/sentinel object)Automatic release tank
Is the data structure ofTwo-way linked list
Automatic release tank
withThe TLS/thread
It 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 incoming
The sentry object
Find the addressThe sentry object
Page where. - In the current page, will be later than
The sentry object
All insertedAutorelese object
Send them all oncerelease
Message and moveNext pointer
Get to the right place. - Clean up from the most recently added object, which can span several pages, until
The sentry object
Page 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)
- call
autoreleaseNoPage
To create ahotPage
- call
page->add(obj)
Adds the object toAutoreleasePoolPage
In the stack, willnext
The pointer pans and points to the next position
- call
-
HotPage not full (current page not full)
- call
page->add(obj)
Adds the object toAutoreleasePoolPage
The stack
- call
-
Have hotPage and page full (current page full)
- call
autoreleaseFullPage
Initialize a new page - call
page->add(obj)
Adds the object toAutoreleasePoolPage
The stack
- call
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
- through
page->releaseUntil(stop)
Through aThe while loop
andNext pointer
To iterate over the callobjc_release(obj)
Release the object untilNext pointer
The loop stops when you point to the top of the stack - And then start
page->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().