preface
OC uses the alloc method to generate objects. What is the underlying principle of alloc? Today we’re going to explore the underlying alloc process.
Output the contents of the object, the address of the object, and the address of the object pointer. The code and print results are as follows:
XJPerson *p1 = [XJPerson alloc];
XJPerson *p2 = [p1 init];
XJPerson *p3 = [p1 init];
XJPerson *p4 = [XJPerson alloc];
NSLog(@"%@-%p-%p", p1, p1, &p1);
NSLog(@"%@-%p-%p", p2, p2, &p2);
NSLog(@"%@-%p-%p", p3, p3, &p3);
NSLog(@"%@-%p-%p", p4, p4, &p4); * * * * * * * * * * * * * * * * * * * * * * operation result * * * * * * * * * * * * * * * * * * * * * * * *2021- 06- 06 15:13:30.196346+0800Alloc&init exploration [1950:46211] <XJPerson: 0x60000295a5a0>-0x60000295a5a0-0x7ffee3355068
2021- 06- 06 15:13:30.196493+0800Alloc&init exploration [1950:46211] <XJPerson: 0x60000295a5a0>-0x60000295a5a0-0x7ffee3355060
2021- 06- 06 15:13:30.196607+0800Alloc&init exploration [1950:46211] <XJPerson: 0x60000295a5a0>-0x60000295a5a0-0x7ffee3355058
2021- 06- 06 15:13:30.196699+0800Alloc&init exploration [1950:46211] <XJPerson: 0x60000295a620>-0x60000295a620-0x7ffee3355050
Copy the code
According to the print analysis:
P1, P2, p3
The contents of the objects and the addresses of the objects are the same, but the addresses of the Pointers are different.p4
和P1, P2, p3
Print object content, object address, pointer address are different.
Why is that? The reasons are as followsIt follows that:
alloc
Method has the ability to open up memory, andinit
Method does not have the ability to open up memory.- The stack allocates memory continuously from high to low, and the heap allocates memory from low to high.
So let’s actually explore what the alloc method does when the object is initialized.
Preparations:
- Download the source objC-818.2.
- Objc4-750 source code compilation, for reference only
Three ways to explore the bottom:
You want to explore the process of alloc, but Xcode cannot see the implementation of alloc directly. Here are three ways to explore the underlying process.
1. Symbolic breakpoint
Set the break point in the alloc method and hold when the break point breakscontrol
+ step into
Jump into assembly code to see the underlying order of method calls (new projects have more assembly code, compiled projects have less), and then add the notation breakpoints known through assembly to explore. The process is as follows:
2. Assembly
Xcode -> Debug -> Debug Workflow -> Always Show Disassembly Explore with control + step into the process or directly by adding symbolic breakpoints in assembly code.
Remark:Build Setting
-> Optimization Level
You can change the compilation optimization level. Different compilation optimization levels display different amounts of assembly code. Higher compilation optimization levels display less assembly code.
3. Symbolic breakpoints Add symbolic breakpoints to methods that need to be explored, such as herealloc
Method is added directlyalloc
But be careful not to activate it until it is needed, otherwise it will be called in many places and interfere with our exploration.
The ultimate method: source debugging
The three methods above give us a way to explore the underlying level, but they are tedious and not clear enough, now that we know from assemblyobjc_alloc
It is to belong tolibobjc.A.dylib
, then go to the Apple open source website to downloadobjc
Source code compiled into a project run to explore, so deep and cool.After you know the source code library through assembly debugging, you can go directly to the Apple open source websiteopensource.apple.com/tarballs/Download the relevant source code and run debugging.
Alloc source exploration
Explore the alloc method through the source code project and find the underlying call process as shown in the figure belowThe exploration process is as follows:
After breaking the alloc method, hold control + Command + step into to enter the alloc method.
+ (id)alloc {
return _objc_rootAlloc(self);
}
Copy the code
Continue to _objc_rootAlloc
id _objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/.true/*allocWithZone*/);
}
Copy the code
So let’s go ahead and go to callAlloc
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__ // Check whether it is Objc2.0
// slowPath (x):x is likely to be false, with a small probability of being true
// FastPath (x):x is likely true
// Remove slowPath and fastPath from the list to tell the compiler to optimize the code
if(slowpath(checkNil && ! cls))return nil;
// Determine if the class implements a custom +allocWithZone. If it does not, enter the if conditional
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 the code
Continue in the order of invocation into _objc_rootAllocWithZone
NEVER_INLINE
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
// allocWithZone under __OBJC2__ ignores the zone parameter
return _class_createInstanceFromZone(cls, 0.nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
}
Copy the code
Continue the above operation to enter_class_createInstanceFromZone
This is the real core code.
Through actual debugging, there are three core methods that have a great impact on OBJ:
cls->instanceSize(extraBytes)
: Calculate the required memory size. Set extraBytes to 0(id)calloc(1, size)
: Applies to the system to open up memory and returns the address pointerobj->initInstanceIsa(cls, hasCxxDtor)
Through:isa
To the corresponding class
We will focus on these three methods:
instanceSize
: Calculates the required memory space
Continue to enter instanceSize
inline size_t instanceSize(size_t extraBytes) const {
if (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;
return size;
}
Copy the code
Continue to cache.FastInstancesize
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
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
returnalign16(size + extra - FAST_CACHE_ALLOC_DELTA16); }}Copy the code
Continue to align16 (16 bytes aligned)
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
Copy the code
Explore the concrete realization of align16 method, taking align16(12) as an example
x = 12;
(x + size_t(15)) & ~ size_t(15) // ~ indicates the inverse
12 + 15 = 27 0001 1011
15 0000 1111
~15 1111 0000
27 & ~15 0001 1011 & 1111 0000Results:0001 0000 16
Copy the code
Summary: The algorithm align16 is actually an integer multiple of 16. I think it’s rounded down, and the reason I’m doing it from a purely algorithmic point of view is that (x + 15) is several times 16, and the excess is erased. For example, (20 + 15) = 35 = 16 * 2 + 3, which is 32. This algorithm is the same as >> 4 << 4, and the result is a multiple of 16, less than 16 erased.
Why is 16-byte alignment needed?
- Data is stored in byte alignment,
cpu
When reading the data, it is ok to read the data in a fixed length of bytes, instead of frequently changing the length of the bytes read, this is a kind of space for time practice. - More secure Since the ISA pointer is 8 bytes in an object, if not aligned, objects will be next to each other, causing access confusion. 16 bytes aligned, will reserve some space, more secure access
calloc
: Applies to the system to open up memory and returns the address pointer
First of all byinstanceSize
Method to calculate the required memory size, and then apply to the systemsize
Size of the memory space returned toobj
, soobj
It’s a pointer to a memory address, so let’s verify that by printing
performcalloc
The addresses printed later have been changed, indicating that the system allocated memory, but with the common object printed<XJPerson: 0x0000000100616e60>
No, why not?
obj
There is no andcls
For the association binding.- It also verifies that
calloc
I just opened up the memory.
initInstanceIsa
Through:isa
To the corresponding class
Continue to objC_Object ::initInstanceIsa
inline void
objc_object::initInstanceIsa(Class cls, boolhasCxxDtor) { ASSERT(! cls->instancesRequireRawIsa()); ASSERT(hasCxxDtor == cls->hasCxxDtor()); initIsa(cls,true, hasCxxDtor);
}
Copy the code
Continue to objc_object::initIsa
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT boolhasCxxDtor) { ASSERT(! isTaggedPointer()); isa_t newisa(0);
if(! nonpointer) { newisa.setClass(cls,this);
} else{ ASSERT(! DisableNonpointerIsa); ASSERT(! cls->instancesRequireRawIsa());#if 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;
}
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
/ /... but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
Copy the code
Memory is associated with the class after newisa.setClass(CLS, this) is discovered through the step over process.
Note: For a detailed exploration of isa structure and source code, see the nature of objects in OC’s underlying principles (iii) Alloc Exploration
Print obj after obj->initInstanceIsa(CLS, hasCxxDtor)
Based on the printed results, conclude that the pointer and class have been associated. So that’s the end of the alloc search.
Summary: The core function of alloc is to allocate memory and associate it with classes via ISA Pointers.