OC underlying principle 01: -alloc-&-init-&-new- source code analysis

OC underlying principle 01: -alloc-&-init-&-new- source code analysis

1. let's take a look at an example to analyze the difference between the memory address and pointer address of the three variables;

The above outputs are

Content, memory address and pointer address
The output result is: Conclusion: Through the above figure, we can find that three objects
Point to the same memory space
, So their
The content and memory address are exactly the same
, But the pointer address of their object is
different

It can be found that p1, p2, and p3 are the same, but the addresses are different, indicating that different pointers point to the same memory space

%p -> &p1: is the pointer address of the object, %p -> p1: is the memory address pointed to by the object pointer Copy code

This is the core content of this exploration. What did alloc do? What did init do?

prepare materials

  • Download  objc4-781  source code
  • To compile the source code, please refer to iOS-Underlying Principle 03: objc4-781 Source Compilation & Debugging

## . objc source code exploration process

  • [First step] First enter according to the alloc method of the Person class in the main function
    alloc
    The source code implementation of the method (that is, the source code analysis starts),
+ (id) alloc { return _objc_rootAlloc(self); } Copy code
  • [Step 2] Jump to
    _objc_rootAlloc
    Source code implementation
id _objc_rootAlloc(Class cls) { return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/); } Copy code
  • [Step 3] Jump to
    callAlloc
    Source code implementation
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false)//the third step of alloc source code { #if __OBJC2__//There are compiler optimizations available /* Reference link: https://www.jianshu.com/p/536824702ab6 */ //checkNil is false, !cls is also false, so slowpath is false, the false value judgment will not go to the if, that is, it will not return nil if (slowpath(checkNil && !cls)) return nil; //Determine whether a class has a custom +allocWithZone implementation, if not, go to the implementation in if if (fastpath(!cls->ISA()->hasCustomAWZ())) { return _objc_rootAllocWithZone(cls, nil); } #endif //No shortcuts available.//No compiler optimizations 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 code

As shown above, in the calloc method, when we are unable to determine which step to achieve, we can use breakpoint debugging to determine which part of the logic to execute. Here is the execution to

_objc_rootAllocWithZone

slowpath & fastpath

Which about

slowpath
with
fastpath
Here needs a brief explanation, these two are macros defined in the objc source code, which are defined as follows

//x is likely to be true, fastpath can be referred to as truth judgment #define fastpath(x) (__builtin_expect(bool(x), 1)) //x is likely to be false, slowpath can be referred to as false value judgment for short #define slowpath(x) (__builtin_expect(bool(x), 0)) Copy code

one of them

__builtin_expect
The instruction is introduced by gcc, 1. Purpose: The compiler can optimize the code to reduce the performance degradation caused by the instruction jump. That is, performance optimization 2. Function:
Allows the programmer to tell the compiler which branch is most likely to be executed.
3. The instruction is written as:
__builtin_expect(EXP, N)
. Means
EXP==N
The probability is very high. 4.
fastpath
In definition
__builtin_expect((x),1)
It means that the value of x is more likely to be true; that is, the chance of executing the statement in if is greater. 5.
slowpath
In definition
__builtin_expect((x),0)
It means that the value of x is more likely to be false. That is, there is a greater chance of executing the statement in the else. 6. In daily development, you can also optimize the compiler through settings to achieve the purpose of performance optimization. The set path is:
Build Setting
-->
Optimization Level
-->
Debug
--> will
None
To
fastest
or
smallest

cls->ISA()->hasCustomAWZ()

among them

fastpath
middle
cls->ISA()->hasCustomAWZ()
It means to judge whether a class has a custom +allocWithZone implementation, here through the breakpoint debugging, there is no custom implementation, so the code in the if will be executed, that is, to
_objc_rootAllocWithZone

  • [Step 4] Jump to
    _objc_rootAllocWithZone
    Source code implementation
id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)//the fourth step of alloc source code { //allocWithZone under __OBJC2__ ignores the zone parameter //zone parameter no longer uses class to create instance memory space return _class_createInstanceFromZone(cls, 0, nil, OBJECT_CONSTRUCT_CALL_BADALLOC); } Copy code
  • [Step 5] Jump to
    _class_createInstanceFromZone
    The source code implementation of this part is the core operation of the alloc source code, as can be seen from the following flowchart and source code, the implementation of this method is mainly divided into three parts
    cls->instanceSize
    : Calculation
    Need to open up
    The size of the memory space
    calloc
    Apply for memory and return address pointer
    obj->initInstanceIsa
    : Associate the class 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)//the fifth step of alloc source code { ASSERT(cls->isRealized());//Check whether it has been implemented //Read class's info bits all at once for performance //Read the bit information of the class at one time to improve performance bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor(); bool hasCxxDtor = cls->hasCxxDtor(); bool fast = cls->canAllocNonpointer(); size_t size; //Calculate the memory size that needs to be opened up, the extraBytes passed in is 0 size = cls->instanceSize(extraBytes); if (outAllocatedSize) *outAllocatedSize = size; id obj; if (zone) { obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size); } else { //Apply for memory 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 (ie isa) 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 code

Points to note: class_getInstanceSize([NSObject class]) returns is

Required memory space

** malloc_size** system

Actual distribution
Memory space

sizeof

type of data
Accounted for
Number of bytes
, Sizeof is the operator, edit is to confirm

According to the source code analysis, the realization flow chart is as follows:

####alloc core operations core operations are located

calloc
Method

#####cls->instanceSize: Calculating the required memory size The execution flow of calculating the required memory size is as follows 1. Jump to

instanceSize
Source code implementation

size_t instanceSize(size_t extraBytes) const { //The compiler quickly calculates the memory size if (fastpath(cache.hasFastInstanceSize(extraBytes))) { return cache.fastInstanceSize(extraBytes); } //Calculate the size of all attributes in the class + the number of extra bytes 0 size_t size = alignedInstanceSize() + extraBytes; //CF requires all objects be at least 16 bytes. //If size is less than 16, the minimum is 16 if (size <16) size = 16; return size; } Copy code

Debugging through breakpoints will execute to

cache.fastInstanceSize
Method to quickly calculate the memory size 2. Jump to
fastInstanceSize
The source code implementation, through the breakpoint debugging, will be executed to
align16

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 compile-time constant, if the value of the parameter EXP is a constant, the function returns 1, otherwise it returns 0 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 //Delete FAST_CACHE_ALLOC_DELTA16 8 bytes added by setFastInstanceSize return align16(size + extra-FAST_CACHE_ALLOC_DELTA16); } } Copy code

3. Jump to

align16
The source code implementation, this method is
16-byte alignment algorithm

//16 byte alignment algorithm static inline size_t align16(size_t x) { return (x + size_t(15)) & ~size_t(15); } Copy code

Memory byte alignment principle

Before explaining why 16-byte alignment is required, we first need to understand the principle of memory byte alignment. There are three main points:

Data member alignment rules
: Data member of struct or union, the first data member is placed in the place where the offset is 0, and the starting position of each data member in the future should be from the size of the member or the size of the member's sub-members (as long as the member has sub-members, For example, data, structure, etc.) start from an integer multiple (for example, int is 4 bytes in a 32-bit machine, and it must be stored from an address that is an integer multiple of 4)
The data member is a structure
: If there are some structure members in a structure, the structure members should be stored from the address that is an integer multiple of the maximum element size (for example: struct a contains struct b, and b contains char, int, double and other elements , Then b should be stored starting from an integer multiple of 8)
The overall alignment rules of the structure
: The total size of the structure, namely
sizeof
The result of must be an integral multiple of its larger internal member, and the shortcomings should be filled. ####Why 16-byte alignment

The reasons for byte alignment are as follows:

Usually memory is composed of bytes. When the CPU accesses data, it is not stored in bytes, but in blocks. The size of the block is the memory access strength. Frequent access to byte-unaligned data will greatly reduce the performance of the cpu, so you can pass

Reduce access times
Count
Reduce CPU overhead
The 16-byte alignment is due to the first attribute in an object
isa
Take up
8
Bytes, of course, an object must have other attributes. When there is no attribute, 8 bytes will be reserved, that is, 16-byte alignment. If not reserved, the isa of this object is next to the isa of other objects. Easy to cause access confusion After 16-byte alignment, you can
Speed up CPU reading
, While making
More secure access
Byte alignment without access confusion-summary

In the byte alignment algorithm, the alignment is mainly

Object
, And the essence of the object is a
struct objc_object
Structure,
Structure
In memory is
Continuous storage
Yes, so you can use this to force the structure. Early Apple is
8
Byte aligned, it is now
16
Byte alignment The following takes align(8) as an example to illustrate the calculation process of the 16-byte alignment algorithm, as shown below 1. the original memory
8
versus
size_t(15)
Add together and get 8 + 15 = 23 Put
size_t(15)
I.e. 15 for
~ (Inverted)
Operation, ~ (inverted) rules are:
1 becomes 0, 0 becomes 1
Finally, the inverse result of 23 and 15 is carried out
&(versus)
Operation, & (and) rules are:
1 is 1 and vice versa is 0
, The final result is 16, that is, the size of the memory is
16
Increased by multiples of

####calloc: request memory, return address pointer passed

instanceSize
The calculated memory size, apply for the size of the memory from the memory, and assign it to obj, so obj is a pointer to the memory address

obj = (id) calloc (1 , size); duplicated code

Here we can use breakpoints to confirm the above statement, when the calloc is not executed,

po obj
for
nil
, After execution, then
po obj
Found, returned a
16
Base address
0x 123456f
Generally, the printing format of an object is similar to this
<LGPerson: 0x01111111f>
(Is a pointer). Why is it not here?

Mainly because

objc
The address has not been matched with the incoming
cls
get on
Associate
, At the same time, it proves that the fundamental role of alloc is
Open up memory

####obj->initInstanceIsa: The association between the class and isa can be known through calloc, the memory has been applied for, and the class has been passed in. Next, you need to associate the class with the address pointer, the isa pointer, and the associated process As shown in the figure below, the main process is to initialize an isa pointer, and point the isa pointer to the requested memory address, and then associate the pointer with the cls class

The above statement can also be confirmed by breakpoint debugging. After initInstanceIsa is executed, an object pointer can be obtained through po obj

<LGPerson: 0x01111111f>
summary

Pass on

alloc
Analysis of the source code shows that the main purpose of alloc is
Open up memory
, And the developed memory needs to be used
16
Byte alignment algorithm, the size of the memory developed now is basically an integer multiple of 16. The core steps of developing the memory are 3 steps:
Calculation - Application - Association

init source code exploration

After exploring the alloc source code, we will explore the init source code. From the source code, we can see that the source code of inti can be implemented in the following two ways.

Class method init

+ (id)init { return (id)self; } Copy code

here

init
Is an
Construction method
, Is through
Factory design (factory method pattern)
, Mainly used to give users
Provide construction method entry
. The reason why id can be used for forced conversion is mainly because
Memory byte alignment
Later, you can use the type to force it to the type you need

Instance method init

  • Explore the instance method init with the following code
LGPerson *objc = [[LGPerson alloc] init]; Copy code
  • by
    main
    middle
    init
    Jump to the source code implementation of init
-(id)init { return _objc_rootInit(self); } Copy code
  • Jump to
    _objc_rootInit
    Source code implementation
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 code

With the above code, what is returned is the passed self itself.

new source code exploration

Generally in development, initialization except

init
, You can also use
new
There is no difference in essence between the two. The following is the source code implementation of new in objc. From the source code, you can know that the callAlloc function (that is, the function analyzed in alloc) is directly called in the new function, and the init function is called, so you can inferred
new is actually equivalent to [alloc init]
Conclusion

+ (id)new { return [callAlloc(self, false/*checkNil*/) init]; } Copy code

However, it is not recommended to use new in general development, mainly because sometimes the init method is rewritten to do some custom operations, such as

initWithXXX
, Will be called in this method
[super init]
, Initializing with new may not be able to go to the custom initWithXXX part.

For example, there are two initialization methods in CJLPerson, one is the init of the overridden parent class, and the other is the custom initWithXXX method, as shown in the figure below

  • When using alloc + init to initialize, the printing situation is as follows

  • When using new initialization, the printing situation is as follows

summary

  • If the subclass does not override the parent's init, new will call the parent's init method
  • If the subclass overrides the parent's init, new will call the init method overridden by the subclass
  • If you use alloc + custom init, it can help us customize the initialization operation, such as passing in some parameters required by the subclass, etc., and eventually will go to the init of the parent class. Compared with new, it has better scalability and more flexible.

Note: You can download the successfully compiled source code of objc-781 on github

supplement

[Question] Why can't the breakpoint be reached?

obj->initInstanceIsa(cls, hasCxxDtor);
?

Mainly because the breakpoint is not the process of the custom class, but the system level