Alloc is something we often encounter in daily development, so let’s take a look at the underlying alloc method at the source level.

Preparation – Get the source code

  • From appleOpen source web sitedownloadobjcThe source code.
  • Follow this tutorial to debug the source code to a compilable state

Begin to explore

Let’s create a new class, JSPerson

//.h
@interface JSPerson : NSObject
@end
//.m
@implementation JSPerson
@end
Copy the code

Initialize JSPerson in the main method

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        JSPerson *person = [JSPerson alloc];
        NSLog(@"%@",person);
    }
    return 0;
}
Copy the code

In JSPerson *person = [JSPerson alloc]; This line adds a breakpoint to run. Press the Control key to find the breakpoint

KCObjcBuild`objc_alloc:
->  0x100003f44 <+0>: jmpq   *0x40be(%rip)             ; (void *)0x0000000100003f76
Copy the code

The objc_alloc method is called, so we make a symbolic break point on objc_alloc, and we continue, and we find that the break point is in the source of objc_alloc.

// Calls [cls alloc].
id
objc_alloc(Class cls)
{
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
Copy the code

Let’s move on to the callAlloc method:

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

So we go through the break point, and we see that we’re at return ((id(*)(id, SEL))objc_msgSend)(CLS, @selector(alloc)); The alloc method is called again, which means callAlloc is called again. So I’m going to continue debugging and this time I’m going to return _objc_rootAllocWithZone(CLS, nil); To break into the _objc_rootAllocWithZone method.

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

The code is simple, let’s follow up with the _class_createInstanceFromZone method:

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; } /// associate the isa pointer 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

The _class_createInstanceFromZone method has three key points, which we analyze below:

Getting the instance sizesize = cls->instanceSize(extraBytes)

The inline function instanceSize is used to get the size of the instance. The size of the object depends on the size of its IVars (member variables).

    // May be unaligned depending on class's ivars.
    uint32_t unalignedInstanceSize() const {
        ASSERT(isRealized());
        return data()->ro()->instanceSize;
    }
    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

If (size < 16) size = 16; AlignedInstanceSize () will continue to call word_align in the inline function

// __LP64__
#   define WORD_MASK 7UL
static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}
Copy the code

This introduces the concept of byte alignment. You can see that WORD_MASK=7, which is used to ensure that the size of the bytes is a multiple of 8.

Allocates memory space to an object

    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        obj = (id)calloc(1, size);
    }
Copy the code

Associate the object with an ISA pointer

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); }Copy the code

conclusion

In summary, the flow chart of alloc is as follows: