Three ways to explore underlying principles
1. Symbolic breakpoints
Start by adding a breakpoint at the create object method. When the program reaches the breakpoint, hold down the Control key and hit Step into to go to objc_alloc. Then add objC_alloc and _objc_rootAllocWithZone symbol breakpoints to see the call process
2. Assembly
Open Xcode -> Debug -> Debug Workflow -> Always Show Disassembly, breakpoints will directly enter assembly, quickly locate the objc_alloc method, and then Debug symbol breakpoints
Objc source code analysis
Check out Apple Open Source, Source Browser (just search for objc on these two sites)
Download the source code is not directly compiled, here is KC configured to compile objC source code, open the latest objC4-818.2 project, run KCObjcBuid, easy debugging.
Source code analysis alloc method call process
You can see the implementation of the alloc method from the point where the object was created
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
+ (id)alloc {
return _objc_rootAlloc(self);
}
Copy the code
But with the breakpoint, it turns out that the objc_alloc method was called first, and then the alloc method was called
// Calls [cls alloc].
id
objc_alloc(Class cls)
{
return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
Copy the code
The key callAlloc method is called twice, the first with objc_msgSend and the second with _objc_rootAllocWithZone
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) { #if __OBJC2__ if (slowpath(checkNil && ! cls)) return nil; 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
Fastpath (x) : optimized in the compiler, easier to execute
Search source code found that Apple in the underlying LLVM on alloc, allocWithZone, and other key methods for an interception processing.
static void fixupMessageRef(message_ref_t *msg) { msg->sel = sel_registerName((const char *)msg->sel); if (msg->imp == &objc_msgSend_fixup) { if (msg->sel == @selector(alloc)) { msg->imp = (IMP)&objc_alloc; } else if (msg->sel == @selector(allocWithZone:)) { msg->imp = (IMP)&objc_allocWithZone; } else if (msg->sel == @selector(retain)) { msg->imp = (IMP)&objc_retain; } else if (msg->sel == @selector(release)) { msg->imp = (IMP)&objc_release; } else if (msg->sel == @selector(autorelease)) { msg->imp = (IMP)&objc_autorelease; } else { msg->imp = &objc_msgSend_fixedup; }}... }Copy the code
Note: sel: method number IMP: method implementation header address
From this, the underlying alloc call process can be summarized
Let’s take a closer look at the core method _class_createInstanceFromZone
_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); } 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(extraBytes); If (outAllocatedSize) *outAllocatedSize = size; id obj; if (zone) { obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size); } else { obj = (id)calloc(1, size); } if (slowpath(! obj)) { if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) { return _objc_callBadAllocHandler(cls); } return nil; } if (! zone && fast) { 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
In particular, instanceSize calculates the required memory size
inline size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
Copy the code
It can be seen that the calculation of memory size can be divided into two cases with or without cache. The following takes the case without cache as an example to study the algorithm of memory size calculation
uint32_t alignedInstanceSize() const { return word_align(unalignedInstanceSize()); } static inline uint32_t word_align(uint32_t x) { //WORD_MASK = 7 return (x + WORD_MASK) & ~WORD_MASK; Uint32_t unalignedInstanceSize() const {// May be unaligned depending on class's ivars. ASSERT(isRealized()); return data()->ro()->instanceSize; }Copy the code
Byte alignment algorithm (x + WORD_MASK) &~ WORD_MASK -> 8+ 7&~ 7 0000 1111 15 0000 0111 7 1111 1000 ~7 0000 1000 15 &~ 7 -> 8 is equivalent to 15 >> 3 < < 3Copy the code
Summary: The size of the object memory is determined by the member variables and is aligned with 8 bytes (multiples of 8), with a minimum of 16 bytes
Isa (structure pointer type) is 8 bytes
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
Copy the code
Why is it at least 16? Isa already takes up 8 bytes without any member variables, so extra space is created for fault tolerance
Why 8-byte alignment? 8-byte objects are the most numerous, and the CPU reads them at a fixed length of 8 bytes, exchanging space for time
BOOL, int, char, etc. store 8 bytes waste, can be combined to read? Move on to the next blog post