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