The opening
Alloc daily development and use of a lot, but rarely to explore its underlying principles, today take time out to explore, record the alloc opening process;
There are several ways to view the underlying principle of alloc
1. Positioning by symbolic breakpoint:
With symbolic breakpoints, the next symbolic breakpoint is called alloc, but the downside is that there are a lot of symbolic breakpoints because there are a lot of places to call the alloc method
2, at [LGPerson alloc] breakpoint, when you reach the breakpoint, hold down the control key to jump into source code
3, in [LGPerson alloc]; When you reach a breakpoint, click Debug –> Debug Workflow to display the source code in assembly mode
Alloc opens up the process
As shown in the figure, qly1 alloc to open up the memory space, qly2, qly3 using 'init' does not change the memory, but also points to qly1 opened up the memory space.Copy the code
Through source tracking, we found that the opening process of alloc is as follows:
Hook procedure
First, through LLVM, look at apple’s underlying alloc principle:
In iOS, when you alloc an object, Apple will do it under LLVM to the systemalloc
Method transformation toobjc_alloc
Method, if yesallocWithZone
Method is converted toobjc_allocWithZone
Methods; The diagram below:
In the LLVM layer, when alloc an object, Apple will first determine whether the object has been marked (hook). Hooks are mainly used by Apple to record data. The following figure
It can be seen from the hook logic that Apple hook is mainly to make special marks for attributes, methods, bits, etc., so as to facilitate the analysis by Apple. The diagram below:
When the hook is over, the callback will execute the methods in objc_alloc and continue to call callAlloc.
Next, we analyze the callAlloc process: In callAlloc, the system determines if the allocWithZone method has been implemented. If not, the system calls objc_msgSend, sends another msgSend, calls alloc again, and passes the alloc method again, then LLVM, On receiving an alloc call, determine if it has been hooked. If so, perform another notification, calling back to the _objc_rootAlloc method.
1, callAlloc
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, Bool allocWithZone = false) {# if current versions objc __OBJC2__ / / judge / * knowledge ⚠ ️ ⚠ ️ ⚠ ️ ⚠ ️ ⚠ ️ ⚠ ️ : #define fastpath(x) (__builtin_expect(bool(x), 1)) #define slowpath(x) (__builtin_expect(bool(x), 0)) __builtin_expect(x,Y/N) This is Unix code that optimizes the code at the assembly level to reduce the number of hops in the code, thereby saving performance; Slowpath (x): slowPath (x): slowPath (x): slowPath (x): slowPath (x): slowPath (x): slowPath (checkNil &&! cls)) return nil; So the judgment goes like this: CallAlloc (CLS, flase, true) checkNil is false, so CLS probably has a value, so the compiler doesn't have to return nil every time. Call return nil if checkNil == true && CLS has a value and the compiler is in non-performing optimization mode) */ if (slowPath (checkNil &&! cls)) return nil; if (fastpath(! CLS ->ISA()->hasCustomAWZ()) {/* knowledge point ⚠️⚠️⚠️⚠️⚠️⚠️ : if the CLS has implemented allocWithZone, if not, the system method is called to open the space. _objc_rootAllocWithZone() If allocWithZone has been implemented, call the following if (allocWithZone) to determine */ return _objc_rootAllocWithZone(CLS, nil); } #endif // No shortcuts available. If (allocWithZone) { Go through the alloc process; return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil); } return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc)); }Copy the code
2. _objc_rootAllocWithZone and _class_createInstancesFromZone
static ALWAYS_INLINE id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, int construct_flags = OBJECT_CONSTRUCT_NONE, bool cxxConstruct = true, size_t *outAllocatedSize = nil) { ASSERT(cls->isRealized()); // Read class's info bits all at once for performance bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor(); bool hasCxxDtor = cls->hasCxxDtor(); bool fast = cls->canAllocNonpointer(); // Whether we can create an object of type nonpointer size_t size; Size = CLS ->instanceSize(extraBytes); if (outAllocatedSize) *outAllocatedSize = size; id obj; // obj is in here, has memory address information, this is a form of dirty memory existence. If (zone) {// Apple used zong to request memory space, but now they pass nil by default. obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size); } else { obj = (id)calloc(1, size); } if (slowpath(! Obj)) {if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {//obj memory was not created successfully, Callback the _objc_callBadAllocHandler method return _objc_callBadAllocHandler(CLS); } return nil; } if (! Zone && fast) {// associate the QLYPerson object with the ISA pointer obj->initInstanceIsa(CLS, hasCxxDtor); } else { // Use raw pointer isa on the assumption that they might be // doing something weird with the zone or RR. obj->initIsa(cls); } if (fastpath(! hasCxxCtor)) { return obj; } construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE; return object_cxxConstructFromClass(obj, cls, construct_flags); }Copy the code
2.1 size = cls->instanceSize(extraBytes)
Source code analysis:
size_t fastInstanceSize(size_t extra) const { ASSERT(hasFastInstanceSize(extra)); if (__builtin_constant_p(extra) && extra == 0) { return _flags & FAST_CACHE_ALLOC_MASK16; } else {/* knowledge ⚠️⚠️⚠️ : FAST_CACHE_ALLOC_MASK = 0x1ff8_flags & 0x1ff8; */ size_t size = _flags & FAST_CACHE_ALLOC_MASK; // remove the FAST_CACHE_ALLOC_DELTA16 that was added // by setFastInstanceSize return align16(size + extra - FAST_CACHE_ALLOC_DELTA16); } } =================== static inline size_t align16(size_t x) { return (x + size_t(15)) & ~size_t(15); } /* knowledge ⚠️⚠️⚠️ : x = 8; X + size_t(15) = 8 + 15 = 23; 0000 0000 0001 0111 ---- 23 binary 0000 0000 0000 1111 ---- 15 binary 1111 1111 1111 0000 ---- ~size_t(15) (15 takes the inverse binary) therefore (x + size_t(15) & ~size_t(15) Binary of 23, combined with the inverse of binary of 15, Take & 0000 0000 0001 0111 ---- 23 binary 1111 1111 1111 0000 ---- 15 Binary 0000 0000 0001 0000 ----- 23 and ~ 15, Finally take the value of & == 16 binary */Copy the code
2.2 Summary of calculation space size:And it turns out that when you alloc space, you need 16 bytes
In apple’s early days, memory was 8 bytes, the current version is 16 bytes; But at present, every object in the software has a corresponding ISA pointer, the memory size of an ISA is 8 bytes;
Memory allocation is continuous, piece by piece, if the object open space, still use 8 bytes, each object will be next to each other, no spare space, in case of access exception, will be accessed to other ISA Pointers, resulting in wild pointer, memory access errors and other problems.
So I’m going to use 16 bytes, so I have reserved space;
16 bytes is also chosen because no matter how an object is extended, it is at least 8 bytes, and the multiple of 8 is chosen to facilitate the efficient and safe processing of the system. Not using 32 or larger multiples of 8 is also to reduce memory waste. So 16 bytes makes more sense.
2.3. Open up space
id obj; If (zone) {// Apple used zong to apply for memory space, but it's obsolete now. obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size); } else {obj = (id)calloc(1, size); obj = (id)calloc(1, size); }Copy the code
2.4. Memory Pointers are associated with objects
if (! Zone && fast) {// object association obj->initInstanceIsa(CLS, hasCxxDtor); } else { // Use raw pointer isa on the assumption that they might be // doing something weird with the zone or RR. obj->initIsa(cls); }Copy the code
2.4.1 Internal resolution of object association
inline void objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor) { ASSERT(! isTaggedPointer()); isa_t newisa(0); if (! Nonpointer: indicates whether to optimize the ias pointer. 0 indicates that the ias pointer is not optimized. It isa pure isa pointer. 1 means to optimize, including address, class information, object reference count, etc. newisa.setClass(cls, this); } else {ASSERT(! DisableNonpointerIsa); ASSERT(! cls->instancesRequireRawIsa()); # SUPPORT_INDEXED_ISA ASSERT(CLS ->classArrayIndex() > 0); newisa.bits = ISA_INDEX_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE newisa.has_cxx_dtor = hasCxxDtor; newisa.indexcls = (uintptr_t)cls->classArrayIndex(); #else newisa.bits = ISA_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE # if ISA_HAS_CXX_DTOR_BIT newisa.has_cxx_dtor = hasCxxDtor; # endif newisa.setClass(cls, this); #endif newisa.extra_rc = 1; // Extra_rc = 1; } isa = newisa; }Copy the code
Tagged Pointer
ASSERT(! isTaggedPointer());
Check whether it is a small object type and return true if it is. Because of small object types, there is noisa
;Tagged Pointer
Small object types; To improve memory management and efficiency, Apple introducedTagged Pointer
; Since variables such as NSNumber, NSDate, NSInteger, and so on, often require less than 8 bytes of memory, so in order to improve the memory footprint, In 64-bit systems, Apple introducedTagged Pointer
.Tagged Pointer
Pointers no longer point to addresses, but to real values. whileTagged Pointer
Actually, it’s not an object;Tagged Pointer
Is to split the object into two parts, one for data, the other for special tags (figure below, Tagged Pointer memory diagram)
After association, the results are as follows: