(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 tomainIn the functionLGPersonOf the classallocMethod 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_rootAllocSource code implementation of
Id _objc_rootAlloc(CLS) {return callAlloc(CLS, false/*checkNil*/, true/*allocWithZone*/); } copy codeCopy the code
  • [Step 3] Jump tocallAllocSource 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_rootAllocWithZoneSource 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 pointer
    • obj->initInstanceIsaWill: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 toinstanceSizeSource 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 tofastInstanceSizeSource 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 toalign16Source 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 structuresStruct 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 A
  • Rules for the overall alignment of structures: the total size of the structure, i.esizeofThe 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 inblockIs 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 timestoReduce CPU overhead
  • 16 byte alignment is due to the first property in an objectisaAccount for8Bytes, 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, okSpeed up the CPU reading speedAnd at the same time makeMore secure access, will not cause access confusion

Byte alignment – summary

  • In the byte alignment algorithm, alignment is mainlyobjectAnd the object is essentially a struct objc_objectThe structure of the body.
  • The structure of the bodyIn memory, yesContinuous deposit, so you can use this point to strongly transform the structure.
  • Apple’s early days were8Byte alignment,nowis16-byte alignment

The following toAlign (8), for example,Figure 16 byte alignment algorithm calculation process, as shown below

  • First the raw memory will be8 与 size_t(15)Add them together, you get 8 plus 15 is equal to 23
  • willsize_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 is16A 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 objfornil, and thenpo objNormal, 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 becauseObjc addressNot yet with the incomingclsTo correlate,
  • It also confirms thatallocIs 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 executioninitInstanceIsaAfter the passpo objYou get a pointer to an object

conclusion

  • Based on theallocAnalysis 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 basically16The 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
  • throughmainIn theinitJump to the source implementation of init
- (id)init { return _objc_rootInit(self); } copy codeCopy the code
  • Jump to_objc_rootInitSource 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