preface

OC objects are initialized by calling the alloc method, so what does the alloc method do underneath to create an object? Today we’ll explore the underlying alloc process.

A quick test is to alloc and init an object, printing the contents of the object, the address of the object, and the address of the pointer to the object, respectively. The code and print result are as follows:

XQPerson *xq1 = [XQPerson alloc]; XQPerson *xq2 = [xq1 init]; XQPerson *xq3 = [xq1 init]; XQPerson *xq4 = [XQPerson alloc]; NSLog(@"%@-%p-%p",xq1,xq1,&xq1); NSLog(@"%@-%p-%p",xq2,xq2,&xq2); NSLog(@"%@-%p-%p",xq3,xq3,&xq3); NSLog(@"%@-%p-%p",xq4,xq4,&xq4); * * * * * * * * * * * * * * * * * * printing result * * * * * * * * * * * * * * * * * * 2022-01-22 17:46:55. 962006 + 0800 AllocDemo [7190-137926] < XQPerson: 0x1004084d0>-0x1004084d0-0x7ffeefbff478 2022-01-22 17:46:55.962547+0800 AllocDemo[7190:137926] <XQPerson: 0x1004084d0>-0x1004084d0-0x7ffeefbff470 2022-01-22 17:46:55.962599+0800 AllocDemo[7190:137926] <XQPerson: 0x1004084d0>-0x1004084d0-0x7ffeefbff468 2022-01-22 17:46:55.962634+0800 AllocDemo[7190:137926] <XQPerson: 0x100408520>-0x100408520-0x7ffeefbff460Copy the code

By analyzing the printing results, it can be concluded that:

The content of XQ1, xQ2, xQ3, the address of the object is the same, but the address of the pointer is different. Xq4 and XQ1, xQ2, xQ3 have different contents, object addresses, Pointers.

It can be concluded that:

1. Alloc opens up memory, init does not. 2. The stack area allocates memory from high to low, and the heap area allocates memory addresses from low to high.

So let’s begin by exploring what the alloc method actually does

Preparations:

1. Download objC-818.2

Objc4-750 source code is compiled for reference only

The exploration process is as follows:

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

Add a breakpoint at XQPerson * xQ1 = [XQPerson alloc. When the program runs to the breakpoint, press Control + Command + step into to enter the alloc method

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

Proceed to _objc_rootAlloc by doing the same above

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

Continue with the same procedure above to enter callAlloc

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

The breakpoint in this method will execute to the _objc_rootAllocWithZone method. Continue to enter the _objc_rootAllocWithZone as above

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

Go ahead and go to _class_createInstanceFromZone, and here’s the core code we really care about.

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

Through actual debugging, it is found that there are three core methods that have a great impact on OBJ:

1.cls->instanceSize(extraBytes); : Calculates the size of the memory space to be opened

2.(id)calloc(1, size); Apply to the system to open up memory, return address pointer

3.obj->initInstanceIsa(cls, hasCxxDtor); Initialize ISA to associate the opened memory space with the current CLS through ISA

The following three methods are analyzed: instanceSize: Calculates the size of memory space to be allocated

Enter the 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

Go to fastInstanceSize(size_t extra)

size_t fastInstanceSize(size_t extra) const { ASSERT(hasFastInstanceSize(extra)); if (__builtin_constant_p(extra) && extra == 0) { return _flags & FAST_CACHE_ALLOC_MASK16; } else { size_t size = _flags & FAST_CACHE_ALLOC_MASK; // remove the FAST_CACHE_ALLOC_DELTA16 that was added // by setFastInstanceSize return align16(size + extra - FAST_CACHE_ALLOC_DELTA16); }}Copy the code

Continue above to go to align16

static inline size_t align16(size_t x) {

    return (x + size_t(15)) & ~size_t(15);

}
Copy the code

Explore the concrete implementation of align16 method below, taking ALIGN16 (8) as an example

x = 8; (x + size(15)) & ~ size_t(15) 8 + 15 = 23 0001 0111 15 0000 1111 ~15 1111 0000 23 & ~15 0001 0111 & 1111 0000 0001, 0000, 16Copy the code

Conclusion: The align16 algorithm is actually taking an integer multiple of 16.

Why is 16-byte alignment needed?

  1. Data is stored in byte alignment,cpuData is read as a fixed byte length, rather than frequently changing the length of the read bytes, which is a space for time approach.
  2. Safer Since isa Pointers are eight bytes in an object, without section alignment, objects will be next to each other, causing access confusion. 16-byte alignment, which leaves some space for more secure access

Calloc: applies for memory allocation to the system and returns the address pointer

InstanceSize calculates the required memory size, applies for a memory size from the system, and returns the memory size to OBJ

It can be seen from the figure that the address changes after calloc is executed, indicating that the system allocates memory space to OBj, but no class information is printed after Po is executed at this time, indicating that Calloc opens up memory space, but obJ and CLS are not bound

InitInstanceIsa Initializes ISA and associates the allocated memory space with the current CLS through ISA

Enter the objc_object: : initInstanceIsa

inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) { ASSERT(! cls->instancesRequireRawIsa()); ASSERT(hasCxxDtor == cls->hasCxxDtor()); initIsa(cls, true, hasCxxDtor); }Copy the code

Enter the objc_object: : initIsa

inline void objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor) { ASSERT(! isTaggedPointer()); isa_t newisa(0); if (! nonpointer) { newisa.setClass(cls, this); } else { ASSERT(! DisableNonpointerIsa); ASSERT(! cls->instancesRequireRawIsa()); #if SUPPORT_INDEXED_ISA ASSERT(cls->classArrayIndex() > 0); newisa.bits = ISA_INDEX_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE newisa.has_cxx_dtor = hasCxxDtor; newisa.indexcls = (uintptr_t)cls->classArrayIndex(); #else newisa.bits = ISA_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE # if ISA_HAS_CXX_DTOR_BIT newisa.has_cxx_dtor = hasCxxDtor; # endif newisa.setClass(cls, this); #endif newisa.extra_rc = 1; } // This write must be performed in a single store in some cases // (for example when realizing a class because other threads // may simultaneously try to use the class). // fixme use atomics here to guarantee single-store and to // guarantee memory order w.r.t. the class index table // ... but not too atomic because we don't want to hurt instantiation isa = newisa; }Copy the code

After obj->initInstanceIsa(CLS, hasCxxDtor) Po prints obj

The pointer is now associated with the class.

Explore alloc method flow chart through source code as follows

Summary: Alloc methods are used to calculate memory size, allocate memory space, and associate classes with isa Pointers.