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