Today’s topic is alloc and init. It’s usually written like [XXX alloc] init. So where do we start? Of course it’s alloc in here. Let’s start with some tips on how to track debugging

One: tracking debugging skills

1.1 Breakpoint Control + step into

objc_alloc
libobjc.A.dylib

1.2 Sign breakpoint

Since it is alloc, it must go to the method of alloc when the breakpoint goes to WYPerson *personA = [WYPerson alloc]; Let’s go to the next sign breakpoint

alloc
rootAlloc

1.3 the assembly

WYPerson *personA = [WYPerson alloc]; Select Debug – Debug Workflow – Always Show Disassembly from the Xcode menu

So let’s make a breakpoint on objc_alloc and then go down and go here

Extension: register read Can read a register

2: alloc

2.1 alloc

We know that alloc allocates memory and returns it, so I want to know what alloc is doing, what should I do?

If you look at objC4-750, after we open the project, we’ll just create a new WYPerson class, initialize an object in main.m, print it, and find that it does return an object. What does alloc actually do? So let’s follow the code that alloc calls _objc_rootAlloc

But, but is it really true? Come, witness the miracle! Through the previous technique of tracing code we found that we would end up with objc_alloc

_objc_rootAlloc
objc_alloc

We see that we do get objc_alloc, and after many checks, this method only goes once, and here we call callAlloc again, just like we did in _objc_rootAlloc below, but the question is why do we do it all over again? In the source code to find such a paragraph, personal guess at the compile stage, judge alloc, and then symbol binding, IMP to objc_alloc, these are low-level processing, and the second go alloc method is normal to go message forwarding!

CallAlloc is called in _objc_rootAlloc

Paste the code for callAlloc here, annotated accordingly

callAlloc(Class cls, bool checkNil, bool allocWithZone=false) {/** fastPath (x) indicates that x is probably not 0 and expects the compiler to optimize it; Slowpath (x) means that x is likely to be 0 and the compiler is expected to optimize -- this means that CLS is likely to have a value that the compiler doesn't have to read every timereturn* / nil instructionsif(slowpath(checkNil && ! cls))return nil;

#if __OBJC2__/* hasCustomAllocWithZone -- there is no alloc/allocWithZone implementation (only classes that do not inherit from NSObject/NSProxy)true) * /if(fastpath(! cls->ISA()->hasCustomAWZ())) { // No alloc/allocWithZone implementation. Go straight to the allocator. // fixme store hasCustomAWZin the non-meta class and 
        // add it to canAllocFastIf (FAST_ALLOC){}else{default is false,} When we look at FAST_ALLOC macro, I notice that I have an else 1 on top of it before I define this macro in the else, */ if (fastPath (CLS ->canAllocFast())) {// No ctors, raw isa, etc. Go straight to the metal. bool dtor = cls->hasCxxDtor(); id obj = (id)calloc(1, cls->bits.fastInstanceSize()); if (slowpath(! obj)) return callBadAllocHandler(cls); obj->initInstanceIsa(cls, dtor); return obj; } else { // Has ctor or raw isa or something. Use the slower path. id obj = class_createInstance(cls, 0); if (slowpath(! obj)) return callBadAllocHandler(cls); return obj; } } #endif // No shortcuts available. if (allocWithZone) return [cls allocWithZone:nil]; return [cls alloc]; }Copy the code

The breakpoint tells us that fastPath (CLS ->canAllocFast()) will not go and will end up in the class_createInstance method. The name of the method probably indicates that it creates an instance, and it does return an object to us. Let’s explore class_createInstance

2.2 Exploring the class_createInstance method

Inside the class_createInstance method, it calls a _class_createInstanceFromZone(CLS, extraBytes, nil) method

id 
class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}
Copy the code

We continue inside the _class_createInstanceFromZone(CLS, extraBytes, nil) method and paste the key code

obj = (id)calloc(1, size)
obj->initInstanceIsa(cls, hasCxxDtor);
initInstanceIsa

2.3 How much memory does an object occupy

Once I was asked this question in an interview, I answered 16 bytes at that time, and others asked why I couldn’t answer it. Going back to the code in the _class_createInstanceFromZone method, size is indeed 16

instanceSize

Size_t instanceSize(size_t extraBytes) {alignedInstanceSize = alignedInstanceSize() + extraBytes; // CF requires all objects to be at least 16 bytes. // The size of the object must be a multiple of 8if (size < 16) size = 16;
        return size;
    }
Copy the code

The WYPerson created by the test doesn’t add anything; the 8 is actually the size of isa

alignedInstanceSize
word_align
unalignedInstanceSize()

uint32_t alignedInstanceSize() {// word_align bytes alignedreturnword_align(unalignedInstanceSize()); // This means that there is no byte aligned content to be byte aligned and return}Copy the code

unalignedInstanceSize()

uint32_t unalignedInstanceSize() { assert(isRealized()); // data is the data segment loaded by dyld, which is the information of the current class. // ro is the properties, methods, and protocols determined at compile time by RO_treturn data()->ro->instanceSize;
    }
Copy the code

Conclusion: Memory alignment is performed for the amount of memory an object occupies. If less than 16 bytes are returned, 16 bytes are returned. This is a space-for-time strategy, so an object must be at least 16 bytes.

2.4 How Do I View Memory Information

Against 2.4.1 LLDB

We add two properties inside the object

X object
x p2

x/4xg
x/4xg p2

2.4.2 Debug – Debug Workflow – View Memory

Three init

Init is actually the simplest thing, the internal implementation doesn’t do anything, it just returns the object, it’s kind of a factory design pattern for us to rewrite init and do some initialization.

We often write like this

- (instancetype)init{
    if(self = [super init]) {// Some initialization of the current class}return self;
}
Copy the code

Alloc flow chart