preface

As an iOS programmer, we basically alloc every day, but what’s going on at the bottom of alloc, we don’t know. Today we’ll explore the underlying alloc process. Before we dive into that, let’s do a simple quiz (don’t rush, take it one step at a time). The contents of the object, the address of the object, and the address code of the object pointer are printed as follows:

    LWPerson * obj = [LWPerson alloc];
    LWPerson * obj1 = [obj init];
    LWPerson * obj2 = [obj init];
    
    LWPerson * newObj = [LWPerson alloc];
 
    NSLog(@"%@---%p--%p",obj,obj,&obj);
    NSLog(@"%@---%p--%p",obj1,obj1,&obj1);
    NSLog(@"%@---%p--%p",obj2,obj2,&obj2);
    NSLog(@"%@---%p--%p",newObj,newObj,&newObj);
Copy the code
    <LWPerson: 0x281404710>---0x281404710--0x16b5cdbe8
    <LWPerson: 0x281404710>---0x281404710--0x16b5cdbe0
    <LWPerson: 0x281404710>---0x281404710--0x16b5cdbd8
    <LWPerson: 0x281404740>---0x281404740--0x16b5cdbd0
Copy the code

Inquiry and analysis draw conclusions

  • obj,obj1,obj2In print, the address of the object is the same, but the address of the pointer is different.
  • newObjobj,obj1,obj2The contents of the print, the address of the object, the address of the pointer are different.

Question: Why exactly? The reasons are as follows:

Conclusion:

  • allocHas the function of opening up a piece of memory, andinitThere is no memory function.
  • The stack areaThe memory that is created ishighAddress tolowThe address,The heap areaIt islowAddress tohighAddress.

So the next thing we want to explore is what does alloc actually do when we initialize the object?

The preparatory work

  1. Download the source objC4-818.2
  2. Compile the source code (to be added later)

Three ways to explore the bottom

Question: We want to explore the alloc process, but find that Xcode does not provide a concrete implementation of alloc, so where to start? Exploratory thinking is important, so be brave and take the first step, so here are three ways to explore the underlying.

1. Symbolic breakpoint

Enter a breakpoint at the point you want to debug, hold down the control key when the breakpoint breaks, then step into the next step, then find the method in assembly, and add a symbolic breakpoint to the method. The specific process is shown in the following figure

2. Assembly (YysD – Eternal God)

Xcode -> Debug -> Debug Workflow -> Always Show Disassembly when a breakpoint is broken The other is to break the jump, hold down the control key, and then step into the next step, this STP into can go all the way, but the middle process is more cumbersome, if you want to give up the trouble, at this time hit a symbol breakpoint. The specific process is shown in the following figure

3. Sign a break point

Add symbolic breakpoints directly to methods that are exploring, as we exploreallocI’ll just give you the methodallocAdd symbolic breakpoints, nothing else, just violence (note: this break point is where you need to break, and then activate it, otherwise it will be called in many places while the project is running and you will explode).

Compile the source code

The above three methods, more complicated, especially the process is more deep, nested more complex, people can not stand. So is there a silky, natural way to do it? Well, if you ask, it’s a must. We already know that objc_alloc belongs to the LibobJC library, so we will compile the objc source code provided by Apple into a project to run, which we are comfortable with. The above preparations are ready, so let’s explore together.

Alloc source exploration

According to the source project, click to enteralloc, the flow chart is as followsThe exploration process is entered at the following breakpointalloc

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

Break to _objc_rootAlloc

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

Copy the code

The break point goes to callAlloc

static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, Bool allocWithZone=false) {#if __OBJC2__ //slowpath(x):x // slowpath(x): slowpath(x): slowpath(x) cls)) return nil; // Check 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

Break to _objc_rootAllocWithZone

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 into the real core code, the breakpoint enters _class_createInstanceFromZone

As you can see from the flowchart and source code, there are three core methods in the _class_createInstanceFromZone method that need to be implemented

  • cls->instanceSize: Calculates the memory size
  • (id)calloc(1, size): Opens up memory and returns the address pointer
  • obj->initInstanceIsa: initializes a pointer associated with the class

The following three methods are mainly analyzed

instanceSize: Calculates the memory size

Break to enter instanceSize

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; // requires all objects to be at least 16 bytes. // Requires all objects to be at least 16 bytes. return size; }Copy the code

Break 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 return align16(size + extra - FAST_CACHE_ALLOC_DELTA16); }}Copy the code

Break point to align16 (16 bytes aligned)

static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}
Copy the code

Let’s explore the concrete implementation of the ALIGN16 method with the example of Align16 (10)

X = 10 (x + size_t(15)) & ~size_t(15) ~(take the negative) 10 + 15 = 25 0001 1001 15 0000 1111 ~15 1111 0000 25 & ~15 0001 1001 & 1111 0000 Result: 0001 0000 16

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

  • cpuData is read in fixed byte blocks. This is a trade in space for time. If the data is read frequently with unaligned bytes, it is reducedcpuPerformance and read speed.
  • More secure because in an objectisaPointers are accounted for8If not aligned, objects will be next to each other, easily causing access confusion.16Byte alignment, will reserve some space, more secure access

calloc: Opens up memory and returns the address pointer

The instanceSize method first calculates the required memory size, then asks the system for the size of memory, and returns it to objc, so objc is a pointer to the memory address, let’s verify by printing the breakpoint method

As can be seen from the figure, OBj has not been assigned a value, and there is an address value, indicating that the system has assigned a dirty address to him

After calloc is executed, a hexadecimal pointer address is printed, indicating that memory has been opened, but it is not the same as the usual address pointer (

). Why?

  • objThere is no andclsBinding the association
  • It also verifies thatcallocI just opened up the memory

initInstanceIsa: initializes a pointer associated with the class

Break point to enter initInstanceIsa

inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) { ASSERT(! cls->instancesRequireRawIsa()); ASSERT(hasCxxDtor == cls->hasCxxDtor()); initIsa(cls, true, hasCxxDtor); }Copy the code

The detailed ISA structure and source code will be explored in a separate post

After the ISA pointer is initialized, print objc

Print the result: the pointer has been associated with the class and the alloc exploration is complete.

initTo explore the

- (id)init {
    return _objc_rootInit(self);
}
Copy the code

Break to _objc_rootInit

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
  • initMethods return the object itself
  • initCan give developers more freedom to customize byidImplement a strong turn and return the type we want

newTo explore the

+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}
Copy the code

So the source code shows you going through callAlloc, and then init, so new is alloc plus init

conclusion

The core purpose of alloc is to open up memory, to associate classes with isa Pointers, to init methods, to give developers more freedom, new is to encapsulate (alloc+init), and not to add other requirements at the time of initialization.