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 downcontrolKey, click againstep intoThe 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_allocMethods the inside

So we got itobjc_allocMethod, 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,debugYou 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_allocSymbols.

When the breakpoint at line 9 is executed, passstep intoInto theobjc_allocMethod 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, enterallocSource code implementation inside

+ (id)alloc {
    return _objc_rootAlloc(self);
}
Copy the code

According to this method_objc_rootAllocLook inside the implementation

id _objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
Copy the code

According to this methodcallAllocLook 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_createInstanceFromZoneCheck 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 theinstanceSizeMethod 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.fastInstanceSizeMethod 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 thealign16Method 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 themcallocThat 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 analysisinitInstanceIsaMethod, initializer pointer, associated with the class to see its implementation
Enter theinitInstanceIsaMethod 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.

inisaAfter the pointer is initialized, printobjcAs 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 ~ ~ ~ ~ ~ ~