Let’s look at an example firstAnalyze the difference between the memory address and pointer address of the three variables;
The above outputs are respectivelyContent, memory address, and pointer address
The output is: Conclusion:In the figure above, we can see three objectsPoint to the same memory space
So theirIdentical in content and memory address
, but the pointer address of their object isdifferent
P1, P2, and P3 are the same, but their 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 to which the object pointer pointsCopy the code
That’s what this quest is all about. What does alloc do? What does init do?
To prepare data
- Download objC4-781 source code
- Compile source code, can refer to iOS- underlying principle 03: OBJC4-781 source code compile & debug
## objC source code exploration process
- [First step] First enter according to the alloc method of the Person class in main function
alloc
Method source code implementation (that is, source code analysis begins),
+ (id) alloc
{
return _objc_rootAlloc(self);
}
Copy the code
- [Step 2] Jump to
_objc_rootAlloc
Source code implementation
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
Copy the code
- [Step 3] Jump to
callAlloc
Source code implementation
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, Bool allocWithZone=false) {#if __OBJC2__ https://www.jianshu.com/p/536824702ab6 * / / / checkNil to false,! CLS is also false, so slowpath is false, and false checks don't go inside if, that is, they don't return nil if (slowpath(checkNil &&! cls)) return nil; // Determine if a class has a custom +allocWithZone implementation. If not, go to the implementation inside if (fastPath (! cls->ISA()->hasCustomAWZ())) { return _objc_rootAllocWithZone(cls, nil); } # 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 the code
As shown above, in the Calloc method, when we are not sure where the implementation is going, we can use breakpoint debugging to determine which part of logic to execute. This is done to _objc_rootAllocWithZone
slowpath & fastpath
Slowpath and FastPath are both macros defined in objC source code as follows
#define fastPath (x) (__builtin_expect(bool(x), 1))) #define slowpath(x) (__builtin_expect(bool(x), 0))Copy the code
The __builtin_expect directive was introduced by GCC. 1. Purpose: The compiler can optimize the code to reduce the performance degradation caused by instruction jumps. Function: Allows the programmer to tell the compiler which branch is most likely to be executed. __builtin_expect(EXP, N) That means that e to the N is very likely. 4. The fastPath definition __builtin_expect((x),1) indicates that the value of x is more likely to be true; Slowpath’s __builtin_expect((x),0) means that x is more likely to be false. 6, in daily development, can also be set to optimize the compiler, to achieve the purpose of performance optimization, set path as: Build Setting –> Optimization Level –> Debug –> change None to fastest or smallest
cls->ISA()->hasCustomAWZ()
Fastpath CLS ->ISA()->hasCustomAWZ() specifies whether a class has a custom +allocWithZone implementation. That is, go to _objc_rootAllocWithZone
- Step 4: Jump to
_objc_rootAllocWithZone
Source code implementation
id _objc_rootAllocWithZone(Class cls, // allocWithZone ignores the value of the hide Return _class_createInstanceFromZone(CLS, 0, nil, OBJECT_CONSTRUCT_CALL_BADALLOC); }Copy the code
- Step 5: Jump to
_class_createInstanceFromZone
This part is the core operation of alloc source code. It can be seen from the following flow chart and source code that the implementation of this method is mainly divided into three partscls->instanceSize
: calculateNeed to open up
Memory size •calloc
: Applies for memory, returns address pointer •obj->initInstanceIsa
: Associates 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) {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(extrabize); 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 (! 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
• class_getInstanceSize([NSObject class]) returns the memory required by the object
•** malloc_size** The actual memory space allocated by the system
• Sizeof Specifies the number of bytes of the data type. Sizeof is an operator that is determined by editing
According to the source code analysis, the realization flow chart is as follows:
####alloc core operations Core operations are located in calloc methods
##### CLS ->instanceSize: Calculate the required memory size The process for calculating the required memory size is as follows• 1. Jump toinstanceSize
Source code implementation
Const size_t instanceSize (size_t extraBytes) {/ / fast calculation memory size if the compiler (fastpath (cache. HasFastInstanceSize (extraBytes))) { return cache.fastInstanceSize(extraBytes); 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 the code
FastInstanceSize (); fastInstanceSize (); fastInstanceSize ()
size_t fastInstanceSize(size_t extra) const { ASSERT(hasFastInstanceSize(extra)); //Gcc's built-in function __builtin_constant_p is used to determine if a value is compile-time. If EXP is constant, the function returns 1. Otherwise return 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 the code
• 3. Jump to the source code implementation of ALIGN16, which is the 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 the code
Memory byte alignment principles
Before explaining why 16-byte alignment is needed, it is important to understand the principles of memory byte alignment
• Data member alignment rules: Struct or union data member, the first data member is placed at offset 0, and the starting position of each data member is an integer multiple of the size of the member or its children (as long as the member has children, such as data, structure, etc.) (for example, int is 4 bytes in 32-bit machines, • Data members are structures: If a structure has some structure members, the structure members are stored from an integer multiple of the size of the largest element in the structure (for example: Char, int, double (struct A, struct B, char, int, double); The total sizeof a structure, the result of sizeof, must be an integer multiple of its internal enlarged members
Byte alignment is required for several reasons:
• Memory is usually composed of bytes. When the CPU accesses and writes data, it does not store data in bytes, but in blocks. The size of blocks is the strength of memory access. Frequently accessed data bytes unaligned, would greatly reduce the performance of the CPU, so can reduce the access number to reduce the overhead of CPU, 16 byte alignment, is because in an object, the first attribute isa account for 8 bytes, an object, of course, there must be other attributes, when no attributes, 8 bytes, 16 byte alignment, If not reserved, the isa of this object and the ISA of other objects are next to each other, which can easily cause access chaos. • 16-byte alignment can speed up CPU reads while making access more secure without causing access chaos
• In byte alignment algorithms, alignment is primarilyobject
And the essence of an object is astruct objc_object
The structure of, •The structure of the body
In memoryContinuous deposit
, so you can use this to perform strong rotation on the structure. • Apple’s early days were8
Byte alignment, now16
Byte alignment The following uses align (8) as an example to illustrate the calculation process of the 16-byte alignment algorithm, as shown below• First place the original memory 8
与 size_t(15)
So if you add them up, you get 8 plus 15 is equal to 23 times omegasize_t(15)
The 15 were~ (take the inverse)
The rule for the operation, ~ (invert) is:1 becomes 0,0 becomes 1
• Finally, take the inverse result of 23 and 15& (and)
The rules for the operation, & (and) are:1 for both, 0 for both
, the final result is 16, that is, the size of memory is16
Is a multiple of
####calloc: applies for memory, returns address pointer The memory size calculated by instanceSize, applies for memory size from memory, and assigns the value to obj. Therefore, obj is a pointer to the memory address
obj = (id)calloc(1, size);
Copy the code
Po obj is nil when calloc is not executed. Po obj returns a hexadecimal address 0x 123456f. The usual print format for an object is
(is a pointer). Why not here?
This is mainly because the objC address has not been associated with the CLS passed in, and confirms that alloc’s primary purpose is to open up memory
####obj->initInstanceIsa: class with ISA association ####obj->initInstanceIsa: class with ISA association ####obj->initInstanceIsa: class with ISA associationThe main process is to initialize an 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 initInstanceIsa is executed, a summary of the object pointer
can be obtained through Po obj
• Through the analysis of the source code of alloc, it can be learned that the main purpose of alloc is to open up memory, and the opened memory needs to use the 16-byte alignment algorithm, and the size of the opened memory is basically an integer multiple of 16 • The core steps of open up memory are three steps: calculation, application and association
Init source code exploration
After exploring the source code of alloc, next explore the source code of init. Through the source code, we know that the source code of INTI has the following two kinds
Methods the init
+ (id)init {
return (id)self;
}
Copy the code
Init here is a constructor that is designed through a factory (factory method pattern), primarily to provide the constructor entry to the user. The main reason why you can use id strong here is that once the memory bytes are aligned, you can use type strong to convert them to the type you want
Instance method init
- Explore the example method init with the following code
LGPerson *objc = [[LGPerson alloc] init];
Copy the code
- through
main
In theinit
Jump to the source implementation of init
- (id)init {
return _objc_rootInit(self);
}
Copy the 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 the code
With the above code, you can return self as passed in.
New source code exploration
In addition to init, you can also use new in development. There is no difference in nature between the two. The following is the source code implementation of new in objC. So you can conclude that new is actually equivalent to alloc init
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
Copy the code
However, it is not recommended to use new in general development, mainly because sometimes the init method will be overridden to do some custom operations, such as initWithXXX, which will call [super init] in this method, and initialization with new may not go to the custom initWithXXX part.
For example, in CJLPerson, there are two initialization methods, the overridden parent init and the custom initWithXXX method, as shown in the figure below
-
When initializing with alloc + init, the following is printed
-
When initializing with new, the print looks like this
conclusion
- If a subclass has not overridden its parent class’s init, new calls the parent class’s init method
- If a subclass overrides its parent’s init, new calls the init method that the subclass overrides
- If you use a custom alloc + init, you’ll be able to customize the initialization, pass in some of the parameters of the subclass, and you’ll end up in the parent class init, which is much more extensible and flexible than new.
Note: The compiled objC-781 source code can be downloaded at Github
supplement
[QUESTION] Why can’t I break itobj->initInstanceIsa(cls, hasCxxDtor);
?
This is mainly because breakpoints stop the flow of custom classes not at the system level