primers
This alloc will be familiar to any single object-oriented iOS programmer every day. This familiar code, also can discover new land oh. Small bench set up, melon seeds, beer, eight-treasure congee… HMM, go straight to the code:
TestPerson *baseP = [TestPerson alloc];
TestPerson *person1 = [baseP init];
TestPerson *person2 = [baseP init];
TestPerson *person3 = [TestPerson alloc];
NSLog(@"%@-------%p-------%p",baseP,baseP,&baseP);
NSLog(@"%@-------%p-------%p",person1,person1,&person1);
NSLog(@"%@-------%p-------%p",person2,person2,&person2);
NSLog(@"%@-------%p-------%p",person3,person3,&person3);
Copy the code
Print the result:
<TestPerson: 0x600001564080>-------0x600001564080-------0x7ffee3e41d28
<TestPerson: 0x600001564080>-------0x600001564080-------0x7ffee3e41d20
<TestPerson: 0x600001564080>-------0x600001564080-------0x7ffee3e41d18
<TestPerson: 0x600001564090>-------0x600001564090-------0x7ffee3e41d10
Copy the code
The baseP, person1, and person2 object addresses are the same, but the pointer addresses are different.
Take a look at person3. Dude, it’s not the same as baseP, person1, person2, not only the object address but also the pointer address…
See here, is it a little doubt 😁 produce what is it what ole question, (note: here only: [have] this option, or small series said tears three thousand miles (^▽^)~ ~ ~ ~ ~ ~)
Person3, baseP, person1, person2
0x7ffee3e41d10 + 0x8 = 0x7ffee3e41d18;
0x7ffee3e41d18 + 0x8 = 0x7ffee3e41d20;
0x7ffee3e41d20 + 0x8 = 0x7ffee3e41d28;
Copy the code
Both are 8-byte offsets and are continuous. BaseP, Person1, person2 are all 0x600001564080, and person3 is 0x600001564090.
So, alloc initializers open up new memory, init doesn’t open up new memory. Stack open space is continuous, open memory is from the high address to the low address.
Of course, when we talk about stacks, we can’t help but think of heap, so heap opens up space, is from the low address to the high address.
See here, is it still relatively hazy, this small series in the end to explain what? Hey, hey, this is the introduction, by the change of address, we can see that the familiar alloc seems to have a lot of unfamiliar parts, so let’s explore the unfamiliar parts of the alloc. (Gods, you can float directly, if there is any insufficiencies, very welcome to be corrected ha…)
Before we can start, we need to do some preparatory work
Go to the Apple Open Source library and download the source code,The source address (Download the source code is not directly run, need to carry out the corresponding configuration, here, xiaobian directly provide a Github inside a good configuration, very good Demo, there are detailed configuration steps —-GitHub)
There are three ways to explore the bottom
When we wrote some demos and wanted to check the specific operation of alloc, Apple only provided the corresponding API, but there was no specific implementation to study. Ohh, you think that’s gonna bring us down?
Here, I share three ways to explore the alloc implementation process (^▽^), (low-key)
Method one: Trace the flow through symbolic breakpoints
Look at the diagram, run the code, and when you get to the breakpoint, you can hold it downcontrol
Key, click againstep into
The step is started. Step to the corresponding underlying symbol breakpoint.
I’m just going to do two steps here, and I’m going to go toobjc_alloc
Methods the inside
So we got itobjc_alloc
Method, you can use symbolic breakpoints for debugging
We can get rid of the initial, atTestPerson*baseP = [TestPerson alloc];
This line of code is broken. Run the project directly using our symbol break.
When we run it, the breakpoint will come directly to our objc_alloc method. We can also see in assembly that the _objc_rootAllocWithZone method is followed by objc_msgSend.
With this method, we break the _objc_rootAllocWithZone method with a symbolic breakpoint, see what methods are called next, and so on, and we can debug where we need to debug. (Aha, the wind has stopped, the rain has stopped, I feel myself again 😁)
Method two, through assembly breakpoint debugging trace
We still have to be thereTestPerson *baseP = [TestPerson alloc];
Set breakpoints. So, at this point,debug
You have to set it to assembly mode.
Setting steps:Xcode -> Debug -> Debug Workflow -> Always Show Disassembly
Once that’s set up, we’re ready to run the project.
With assembly, the current breakpoint is at line 7, and we set a breakpoint at line 9 to breakobjc_alloc
Symbols.
When the breakpoint at line 9 is executed, passstep into
Into theobjc_alloc
Method inside.
Of course, this can also be done by holding down the Control key and then clicking Step into to start a single step.
Method 3: Directly add the symbol breakpoint trace you want to search
For example, if you want to break the alloc method, just hit the sign breakpoint (simple, Cu bao).
Oh oh, that’s good, one step, but… You can only activate the breakpoint at the point where you want to break. You can create a breakpoint on the line before the breakpoint. When it does, set the symbolic breakpoint.
If you’re active the whole time, it’s cool to think about how many alloc calls there are in the project. (Lend you a hammer, alloy 😁)
Congratulations to the reader, three debugging methods are mentioned. But, see but… These three methods, although effective, but the efficiency is too low, every time have to continue to track breakpoints, not only tedious, but also very troublesome. Debugging, a careless, hand shaking, click a few times, skipped a method, not to have to start again, speaking of, a tear ah, must think of a more stable method. Uh huh. Go to the bathroom and think…
If we compile the objC source code provided by Apple into a project and run it, then we can do what we want
Through source code compilation, explore the source code of alloc
Download the compiled source code mentioned in the previous article and run it to track the specific flow of alloc.
First of all, break point, enteralloc
Source code implementation inside
+ (id)alloc {
return _objc_rootAlloc(self);
}
Copy the code
According to this method_objc_rootAlloc
Look inside the implementation
id _objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
Copy the code
According to this methodcallAlloc
Look inside the implementation
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, Bool allocWithZone=false) {#if __OBJC2__ = objc2.0 // slowPath =false // fastPath = true // SlowPath and fastPath should be removed without affecting any functionality at all. if (slowpath(checkNil && ! cls)) return nil; If (fastPath (!) {// 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
Next, look at the implementation of the _objc_rootAllocWithZone method
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
At this point, we’re in the real core code area, the create section, according to the method_class_createInstanceFromZone
Check the implementation
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); Obj = (id)calloc(1, size); 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 CLS class with 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 the code
According to our combing process, we can get a flow chart
Next, the details are analyzed again
The first is where the memory size is calculated
Enter theinstanceSize
Method to view the implementation
The inline size_t instanceSize (size_t extraBytes const) {/ / fast computational memory size if (fastpath (cache. HasFastInstanceSize (extraBytes))) { return cache.fastInstanceSize(extraBytes); Size_t size = alignedInstanceSize() + extraBytes; // CF requires all objects to be at least 16 bytes. // Minimum return 16 bytes if (size < 16) size = 16; return size; }Copy the code
To enter thecache.fastInstanceSize
Method to view the implementation
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 return align16(size + extra - FAST_CACHE_ALLOC_DELTA16); }}Copy the code
Enter thealign16
Method to view the implementation
Return (x + size_t(15)) &~ size_t(15); return (x + size_t(15)) &~ size_t(15); return (x + size_t(15)) &~ size_t(15); }Copy the code
Further study the specific process of align16, which requires some basic bit arithmetic 😁
X + size_t(15) &~ size_t(15) &: is an and ~ : is an inverse & (and) -- if all values are 1, the value is 1; otherwise, 0 ~ (inverse) -- If 1 becomes 0, 0 becomes 1. Take 9 as an example. 0001 1000 (binary) 15:00 00 1111 15 (inverse) : 1111 0000 0001 1000 1111 0000 = 0001 0000 Result: 16Copy the code
So we get: method align16, which essentially takes n * 16 (n is a positive integer), such as (m + 15) = sum,m is any positive integer, sum is a multiple of 16, sum / 16, and you get an integer with the remainder dropped. This is 16 byte alignment.
So why16-byte alignment
?
1. Improve performance and read speed. Because the CPU reads data in byte blocks, if the frequent reading of unaligned bytes reduces the CPU performance and read speed, so this is actually to exchange space for time.
2. Safer. Is 16 bytes alignment, rather than 8 byte alignment, because the object of the isa pointer is of 8 bytes, if only 8 bytes of space unit, a isa pointer is full, no room, may cause the object isa pointer and isa pointer to another object, easy access to confusion. So 16-byte alignment, which leaves some space, makes access more secure
After analyzing memory calculations, you have to continue analyzing themcalloc
That is, open up memory, return the address pointer, enter the method inside to see the implementation
The instanceSize method calculates the required memory space, and then applies for a size of memory from the system and assigns it to objC, so objC is a pointer to the memory address.
Check it out:
As can be seen from the figure, when the first breakpoint, obj has not been assigned, at this time there is an address value, indicating that the system opened a placeholder memory address. The address (pointer address) printed after the calloc method is executed;
This is different from the common address pointer (
).
1, because the obJ address has not been associated with the CLS passed in;
Calloc is just memory space;
Then analysisinitInstanceIsa
Method, initializer pointer, associated with the class to see its implementation
Enter theinitInstanceIsa
Method to view the implementation
inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) { ASSERT(! cls->instancesRequireRawIsa()); ASSERT(hasCxxDtor == cls->hasCxxDtor()); initIsa(cls, true, hasCxxDtor); }Copy the code
After calloc is executed, memory is allocated, initInstanceIsa initializes the ISA pointer, points the ISA pointer to the allocated memory address, and associates the ISA pointer with the CLS class.
inisa
After the pointer is initialized, printobjc
As a result of the
At this point, oh yeah, done, the bottom of alloc exploration, is completed, there is no harvest ah, (do not allow no ah <( ̄▽ ̄)/)
In fact, the main purpose of alloc is to open up memory and associate isa Pointers with CLS classes.
Thank you for coming to ~ ~ ~ ~ ~ ~