preface

This is the first day of my participation in Gwen Challenge

As an iOS developer, ALLOc is what I contact with most in daily life. Although I know the function of ALLC is to open up memory space for objects, I don’t know the principle and logic behind it. This article shares how to gain insight into Alloc with objC’s source code.

The preparatory work

The source code for objc is officially opensource at apple and can be downloaded from the official opensource. The latest is 824, but can not download recently, you can download 818, should be very good.

The official source code requires some configuration to run, here for referenceMacOS 11.1 Xcode12.2 objc818 source code compiled

Explore with source code

  1. Create an object
  Person *person = [Person alloc];

Copy the code

2. Click alloc to enter the implementation method.

+ (id)alloc {
    return _objc_rootAlloc(self);
}
----
id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/.true/*allocWithZone*/); } -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

You can see that callAlloc is the core method. The __OBJC2__ macro stands for objc2.0, which is the version we currently use.

Slowpath and FastPath macros are defined as follows. Their function is to tell the compiler which branch is most likely to be executed. The compiler will adjust the structure of the instruction during compilation to reduce instruction hops and achieve the purpose of optimization. Here the CLS is most likely to have a value.

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

3) I don t know at this time! CLS ->ISA()->hasCustomAWZ()).

4. Successfully abort the callAlloc of the target object Person and arrive at this code

You keep going down and you find yourself

The call stack is as followsThus it can be seen[Person alloc]Call isobjc_allocAnd came tocallAllocAnd why did it come hereobjc_alloc, will be explored in subsequent articles. And then it will come againcallAlloc, is called_objc_rootAllocWithZoneThis method.

5. You can see that the core method _class_createInstanceFromZone is finally called.

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);
}
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
  1. _class_createInstanceFromZoneThree things were done:
  • cls->instanceSize: Calculates the size of memory space to be allocated
  • calloc: Applies for memory, returns address pointer
  • Obj - > initInstanceIsa:The class andisaassociated

7. It can be seen that the memory size required by an instance object of a class will be fetched from the cache first. In fact, the memory size is already calculated when the class is loaded into the memory, so it can be fetched directly from the cache in most cases. __builtin_constant_p is a built-in function of Gcc to determine if extra is a compile constant, where extra is not valid, else branch, fetch the size, and align with 16 bytes, align16 is the 16 byte alignment method.

    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.
        // Minimum value is 16
        if (size < 16) size = 16;  
        return size;
    }
    
     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
            returnalign16(size + extra - FAST_CACHE_ALLOC_DELTA16); }} -- -- -- -- -- -- -- -- -- -- -- --// 16-byte alignment algorithm
   static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15); }}Copy the code

8. Calculate the required size of memory for Calloc, and return a pointer to the memory address. After calloc, print the value of obj before the change address is associated with the class.

9. As shown in figureobj->initInstanceIsaAfter that, print the value of obj, which is now associated with the class.

conclusion

The core method _class_createInstanceFromZone does three things: calculate the amount of memory that needs to be allocated, apply for memory, return an address pointer to associate the class with ISA, and finally return the address pointer. Attached is the final flow chart.