preface

As a programmer working on iOS, the most commonly used method is alloc, so what does allo actually do at the bottom? First, we write a test code as follows:

  SPPerson *p1 = [SPPerson alloc];
  SPPerson *p2 = [p1 init];
  SPPerson *p3 = [p1 init];
  SPPerson *p4 = [SPPerson alloc];
    
  NSLog(@"%@-%p-%p",p1,p1,&p1);
  NSLog(@"%@-%p-%p",p2,p2,&p2);
  NSLog(@"%@-%p-%p",p3,p3,&p3);
  NSLog(@"%@-%p-%p",p4,p4,&p4);
Copy the code

The console output is as follows:

  <SPPerson: 0x600000050410>-0x600000050410-0x7ffee943e098
  <SPPerson: 0x600000050410>-0x600000050410-0x7ffee943e090
  <SPPerson: 0x600000050410>-0x600000050410-0x7ffee943e088
  <SPPerson: 0x600000050420>-0x600000050420-0x7ffee943e080
Copy the code

By analyzing the output results, we found that:

    1. The contents of p1, p2, and p3 point to the same address, but the addresses of Pointers are different
    1. The contents of p4 and P1, P2, p3, the addresses of the objects they point to, the addresses of the Pointers are different

We conclude that:

Alloc can open up a chunk of memory, whereas init does not. The stack area opens up memory from high address to low address, and the heap area is low address to high address.

So it looks like alloc is still allocating memory, so how does alloc actually allocate memory? We click on alloc and we get to:

+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
Copy the code

And then I couldn’t get it in, so I had to find another way

Three basic approaches to low-level exploration

First: symbolic breakpoints:

Console hold down Control + Step into and see objc_alloc and add a sign breakpoint to objC_Alloc

Second: assembly debugging:

Xcode -> Debug -> Debug Workflow -> Always Show Disassembly

Second: sign breakpoint, judge position:

Alloc is a symbolic breakpoint, which is a breakpoint that is broken when the code is executed

Through the analysis of the above three methods, we find that alloc is actually called

libobjc.A.dylib`objc_alloc

So we downloaded the source code, solved the compilation problems and started analyzing the source code

Source code analysis

We search alloc {and come up

+ (id)alloc {
    return _objc_rootAlloc(self);
}
Copy the code

Point into _objc_rootAlloc:

id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/.true/*allocWithZone*/);
}
Copy the code

Point into callAlloc:

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

CallAlloc is first a macro __OBJC2__ which is defined as:

// Define __OBJC2__ for the benefit of our asm files.
#ifndef __OBJC2__
#   if TARGET_OS_OSX  &&  !TARGET_OS_IOSMAC  &&  __i386__
        // old ABI
#   else
#       define __OBJC2__ 1
#   endif
#endif
Copy the code

This definition is in objC4-787.1, but not in objC4-818.2, presumably because the compiler automatically adds this macro and then fastPath and slowPath, click to see

#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))
Copy the code

Fastpath (x):x is most likely true, slowPath (x):x is most likely false and then through the breakpoint we go to _objc_rootAllocWithZone and click in:

_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

Click on _class_createInstanceFromZone:

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

Oh, my God. How do you analyze a chunk of code like that? Let’s focus on the return value, return obj, and then see what the obj assignment does, analyzing the core three steps:

  • 1. InstanceSize the space needed to calculate and allocate objects
  • 2. Calloc, allocate space
  • 3. InitIsa, associate the object to isa pointer

Step 1 instanceSize, click:

    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

See less than 16 bytes returned 16, then alignedInstanceSize and extraBytes, via annotation extraBytes actually is depending on class’s ivars. AlignedInstanceSize is byte aligned, click in:

static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}
static inline size_t word_align(size_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}
static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}
Copy the code
  • (x + 7) &~ 7 is equivalent to (x + 7) >> 3 << 3
  • (x + 15) &~ 15 is equivalent to (x + 7) >> 4 << 4

The flow chart