(iOS)
Before analyzing the alloc source code, let’s look at the differences between the three variables: memory address and pointer address:
Output for each of three objectsContent, memory address, pointer address
, below is the print result
Conclusion: As can be seen from the figure above, the three objects point to the same memory space, so their contents and memory addresses are the same, but the addresses of the objects’ Pointers are different
%p -> &p1: is the address of the object pointer, %p -> p1: is the memory address to which the object pointer points
That’s what this article explores. What does Alloc do? What does init do?
The preparatory work
- Download the objC4-781 source code
- To compile the source code, see iOS- Underlying Principles 03: ObjC4-781 Source code Compilation & Debugging
Alloc source exploration
Alloc + init overall source code exploration process is as follows
- 【 Step 1 】 First of all, according to
main
In the functionLGPerson
Of the classalloc
Method goes into the source implementation of the Alloc method (that is, source analysis begins),
//alloc source analysis - step 1 + (id)alloc {return _objc_rootAlloc(self); } copy codeCopy the code
- [Step 2] Jump to
_objc_rootAlloc
Source code implementation of
Id _objc_rootAlloc(CLS) {return callAlloc(CLS, false/*checkNil*/, true/*allocWithZone*/); } copy codeCopy the code
- [Step 3] Jump to
callAlloc
Source code implementation of
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, Bool allocWithZone=false)// alloc source step 3 {#if __OBJC2__ // compiler optimizations are available /* reference link: https://www.jianshu.com/p/536824702ab6 * / / / checkNil to false,! If (slowpath(checkNil &&! cls)) return nil; // Check if a class has a custom +allocWithZone implementation. If not, go to the if implementation if (fastPath (! cls->ISA()->hasCustomAWZ())) { return _objc_rootAllocWithZone(cls, nil); } #endif // No shortcuts available. // If (allocWithZone) {return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil); } return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc)); } copy codeCopy the code
As shown above, in the Calloc method, when we are not sure where the implementation has gone, breakpoint debugging can determine which part of the logic the execution has gone through. Here it goes to _objc_rootAllocWithZone
slowpath & fastpath
Slowpath and FastPath are both macros defined in the objC source code and are defined as follows
#define fastpath(x) (__builtin_expect(bool(x), 1)) #define slowPath (x) (__builtin_expect(bool(x), 0)) copy codeCopy the code
The __builtin_Expect directive was introduced by GCC. 1. Purpose: The compiler can optimize the code to reduce performance degradation caused by instruction jumps. 2. Function: Allows the programmer to tell the compiler which branch is most likely to execute. 3, instruction: __builtin_expect(EXP, N) So the probability that e to the EXP is equal to N is high. 4, fastPath definition __builtin_expect((x),1) is more likely to be true; 5. There is a greater chance that __builtin_expect((x),0) in slowPath’s definition means that x has a false value. 6. In daily development, you can also optimize the compiler to achieve performance optimization by setting the path: Build Setting –> Optimization Level –> Debug –> None = fastest or smallest
cls->ISA()->hasCustomAWZ()
CLS ->ISA()->hasCustomAWZ() ->hasCustomAWZ() That is, go to _objc_rootAllocWithZone
- [Step 4] Jump to
_objc_rootAllocWithZone
Source code implementation of
id _objc_rootAllocWithZone(Class cls, Ignores the zone parameter. // allocWithZone under __OBJC2__ ignores the zone parameter. // The zone parameter is no longer used Return _class_createInstanceFromZone(CLS, 0, nil, OBJECT_CONSTRUCT_CALL_BADALLOC); } copy codeCopy the code
-
[Step 5] jump to _class_createInstanceFromZone source code implementation, this part is the core operation of alloc source code, from the following flowchart and source code, the implementation of the method is mainly divided into three parts
cls->instanceSize
: Computing needs to open upMemory space size
calloc
:Application memory
, returns the address pointerobj->initInstanceIsa
Will:Class is associated with ISA
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)// alloc source 5 {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(); size_t size; Size = CLS ->instanceSize(extraBytes); if (outAllocatedSize) *outAllocatedSize = size; id obj; if (zone) { 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) { return _objc_callBadAllocHandler(cls); } return nil; } if (! Zone && fast) {// associate the CLS class with the obj 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 codeCopy the code
According to the source code analysis, the realization flowchart is as follows:
Alloc Core operation
The core operations are all in the Calloc method
CLS ->instanceSize: Calculate the required memory size
The execution process for calculating the size of memory to be created is as follows
- 1. Jump to
instanceSize
Source code implementation of
Const size_t instanceSize (size_t extraBytes) {/ / fast calculation memory size if the compiler (fastpath (cache. HasFastInstanceSize (extraBytes))) { return cache.fastInstanceSize(extraBytes); 0 size_t size = alignedInstanceSize() + extraBytes; // CF requires all objects be at least 16 bytes. // if (size < 16) size = 16; // CF requires all objects be at least 16 bytes. return size; } copy codeCopy the code
Breakpoint debugging will be executed to cache. FastInstanceSize method, quickly calculate memory size
- 2. Go to
fastInstanceSize
Source code implementation, through breakpoint debugging, will be executed toalign16
size_t fastInstanceSize(size_t extra) const { ASSERT(hasFastInstanceSize(extra)); //Gcc's built-in function __builtin_constant_p is used to determine whether a value is a compilation time. If the value of the argument EXP is constant, the function returns 1. If (__builtin_constant_p(extra) && extra == 0) {return _flags & FAST_CACHE_ALLOC_MASK16; } else { 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); // Remove the FAST_CACHE_ALLOC_DELTA16 added by setFastInstanceSize. }} Copy the codeCopy the code
- 3. Go to
align16
Source code implementation, this method is16 byte alignment algorithm
Static inline size_t align16(size_t x) {return (x + size_t(15)) & ~size_t(15); } copy codeCopy the code
Memory byte alignment principle
Before explaining why 16-byte alignment is needed, it is important to understand the principles of memory byte alignment
Data member alignment rules
: For struct or union data members, the first data member is placed at offset 0, and each subsequent data member is stored starting with an integer multiple of the size of that member or its children (as long as the member has children, such as data, structures, etc.) (for example, int is 4 bytes in 32-bit machines). Start with an integer multiple of 4.)Data members are structures
Struct b: struct b: struct b: struct b: struct b: struct b: struct b: struct b: struct b: struct b: struct b: struct b: struct b: struct b: struct b: struct a: struct b: struct b: struct B: struct B: struct B: struct B: struct B: struct B: struct B: struct B: struct B: struct B: struct A: struct b: struct B: struct B: struct B: struct ARules for the overall alignment of structures
: the total size of the structure, i.esizeof
The result must be an integer multiple of its internal larger member, and the shortfall must be made up
Why is 16-byte alignment needed
Byte alignment is required for the following reasons:
- Memory is usually made up of bytes. The CPU does not store data in bytes, but in
block
Is the unit access, block size is the memory access intensity. Frequent access to byte unaligned data greatly reduces CPU performance, so you can passReduce access times
toReduce CPU overhead
- 16 byte alignment is due to the first property in an object
isa
Account for8
Bytes, of course, an object must have other properties. When there are no properties, 8 bytes are reserved, that is, 16-byte alignment. If not reserved, the ISA of this object is next to the ISA of other objects, causing access confusion - 16 bytes aligned, ok
Speed up the CPU reading speed
And at the same time makeMore secure access
, will not cause access confusion
Byte alignment – summary
- In the byte alignment algorithm, alignment is mainly
object
And the object is essentially a struct objc_objectThe structure of the body
. The structure of the body
In memory, yesContinuous deposit
, so you can use this point to strongly transform the structure.- Apple’s early days were
8
Byte alignment,now
is16-byte alignment
The following toAlign (8), for example,
Figure 16 byte alignment algorithm calculation process, as shown below
- First the raw memory will be
8
与size_t(15)
Add them together, you get 8 plus 15 is equal to 23 - will
size_t(15)
The 15 were~ (take the opposite)
Operation,~ (take the opposite)
The rules are:1 becomes 0,0 becomes 1
- Finally, take the inverse of 23 and 15
& (and)
Operation,& (and)
The rules are:One is one, and zero is zero
, the final result is 16, that is, the memory size is16
A multiple of theta
Calloc: Allocates memory and returns an address pointer
The memory size calculated by instanceSize is applied to memory with size size and assigned to obj, so obj is a pointer to the memory address
obj = (id)calloc(1, size); Copy the codeCopy the code
Here we can verify this statement with a break point, in which the calloc is not executed,po obj
fornil
, and thenpo obj
Normal, returns a hexadecimal address
In normal development, an object would normally be printed in a format like
(which is a pointer). Why not here?
- Mainly because
Objc address
Not yet with the incomingcls
To correlate, - It also confirms that
alloc
Is the fundamental function ofCreate a memory
Obj ->initInstanceIsa: The class is associated with ISA
After calloc, we can know that the memory has been applied for and the class has been passed in. Next, we need to associate the class with the address pointer, namely the ISA pointer. The associated flowchart is as follows
The main process is to initialize an ISA pointer, point the ISA pointer to the requested memory address, and then associate the pointer with the CLS class
You can also use breakpoint debugging to verify the above statement, after the executioninitInstanceIsa
After the passpo obj
You get a pointer to an object
conclusion
- Based on the
alloc
Analysis of the source code shows that the main purpose of alloc isCreate a memory
, and open up the memory needs to be used16 byte alignment algorithm
, now open up the size of memory is basically16
The integer times of - There are three core steps to creating memory:
Calculate -- apply -- correlate
Init source code Exploration
Alloc source explored, then explore the init source, through the source code, inTI source implementation has the following two
Methods the init
+ (id)init { return (id)self; } copy codeCopy the code
In this case init is a constructor that is designed through a factory (factory method pattern) and is primarily used to provide the user with access to the constructor. The main reason why you can use id strong conversion here is that after memory byte alignment, you can use type strong conversion to the type you want
Instance method init
- Explore the instance method init with the following code
LGPerson *objc = [[LGPerson alloc] init]; Copy the codeCopy the code
- through
main
In theinit
Jump to the source implementation of init
- (id)init { return _objc_rootInit(self); } copy codeCopy the code
- Jump to
_objc_rootInit
Source code implementation of
id _objc_rootInit(id obj) { // In practice, it will be hard to rely on this function. // Many classes do not properly chain -init calls. return obj; } copy codeCopy the code
If you have the code above, you’re going to return the self that was passed in.
New source exploration
The following is the source code implementation of new in objc. The source code shows that the new function directly calls callAlloc (that is, the function analyzed in alloc), and calls init. So we can conclude that new is the same thing as alloc init
+ (id)new { return [callAlloc(self, false/*checkNil*/) init]; } copy codeCopy the code
However, it is not recommended to use new in general development, mainly because sometimes you overwrite the init method to do some custom operations, such as initWithXXX, and call [super init] in this method. Initializer with new may not go to the custom initWithXXX part.
For example, there are two initialization methods in CJLPerson, the overridden parent init and the custom initWithXXX method, as shown in the figure below
- When alloc + init is used, the following is printed
- With the new initialization, the print is as follows
conclusion
- If a subclass does not override the parent init, new calls the parent init method
- If a subclass overrides a parent init, new calls the init method that the subclass overrides
- If you use alloc + custom init, it will allow you to customize the initialization, for example, passing in some of the parameters that the subclass needs, and eventually it will also lead to the superclass’s init, which is much more scalable and flexible than new.
Note: The compiled source code of OBJC-781 is available for download at Github
supplement
【 Question 】 Why can’t break pointobj->initInstanceIsa(cls, hasCxxDtor);
?
This is mainly because the breakpoint is not breaking the flow of the custom class, but at the system level