A preliminary study of alloc method in OC

Keywords: github

Foreword

We often use XZPerson *p= [[XZPerson alloc]init] in development. We only know that in this way, we can create a new object and use it directly, We can assign values to this object, but we haven't cared about how the underlying layer of the alloc method is implemented. Today, I'm going to make an attempt to mine the underlying layer of the alloc method. First, we need to prepare a Compiled objc4_code , you can download and explore directly;

First of all, the previous alloc flow chart I explored and completed is for your reference

 

 

Then we directly use the objc-752 source code, and write the following code in main.m to start the exploration of alloc.

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        XZPerson *object = [XZPerson alloc];
        NSLog(@"Hello, World! %@",object);

    }
    return 0;
}

The break point is directly here. Click "alloc" to enter "objc" rootalloc ". In fact, we found that the breakpoint is objc" alloc ".

Here we will explain a little: we can see that there is such a method fixupMessageRef in the source code. Here, we should bind alloc to objc "alloc" method through symbol binding, specifically through LLVM period. We will not introduce it in detail in this article at first;

fixupMessageRef(message_ref_t *msg)
{    
    msg->sel = sel_registerName((const char *)msg->sel);

    if (msg->imp == &objc_msgSend_fixup) { 
        if (msg->sel == SEL_alloc) {
            msg->imp = (IMP)&objc_alloc;
        } else if (msg->sel == SEL_allocWithZone) {
            msg->imp = (IMP)&objc_allocWithZone;
        } else if (msg->sel == SEL_retain) {
            msg->imp = (IMP)&objc_retain;
        } else if (msg->sel == SEL_release) {
            msg->imp = (IMP)&objc_release;
        } else if (msg->sel == SEL_autorelease) {
            msg->imp = (IMP)&objc_autorelease;
        } else {
            msg->imp = &objc_msgSend_fixedup;
        }
    } 
    else if (msg->imp == &objc_msgSendSuper2_fixup) { 
        msg->imp = &objc_msgSendSuper2_fixedup;
    } 
    else if (msg->imp == &objc_msgSend_stret_fixup) { 
        msg->imp = &objc_msgSend_stret_fixedup;
    } 
    else if (msg->imp == &objc_msgSendSuper2_stret_fixup) { 
        msg->imp = &objc_msgSendSuper2_stret_fixedup;
    } 
#if defined(__i386__)  ||  defined(__x86_64__)
    else if (msg->imp == &objc_msgSend_fpret_fixup) { 
        msg->imp = &objc_msgSend_fpret_fixedup;
    } 
#endif
#if defined(__x86_64__)
    else if (msg->imp == &objc_msgSend_fp2ret_fixup) { 
        msg->imp = &objc_msgSend_fp2ret_fixedup;
    } 
#endif
}

1. First, we can see that the internal call of alloc, objc, rootalloc method and performance call of callAlloc method are entered

+ (id)alloc {
    return _objc_rootAlloc(self);
}

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

Here we can see that there is a parameter of Class in the objcrootAlloc method

Class The underlying class is objc_class Class structure
typedef struct objc_class *Class;

objc_class In structure
struct objc_class : objc_object {
   // Class isa; / / ISA pointer
    Class superclass;  //Parent class
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    //... there are many other methods not posted here. You can download your own code to check them

OBC Class inherits from OBC object, which stores a Class structure of isa

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

If there are students who have used RunTime here, they can see some familiar things ivars, methodLists and protocols. We will elaborate on this later article, which will be introduced a little first

2. When entering the callAlloc method

Here I have the reasons to judge. If there are any mistakes, please see the comments. We can discuss them together. If we interrupt here, we can see that most of them will go directly to the class "createinstance method;

callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    /**
     fastpath(x)Indicates that x is not likely to be 0, and the compiler is expected to optimize;
    slowpath(x)It means that x is likely to be 0, and the compiler is expected to optimize it -- here it means that there is a value in the cls rate. The compiler can not read the return nil instruction every time
     */
    if (slowpath(checkNil && !cls)) return nil;

#if __OBJC2__
    /**
     The actual meaning is hasCustomAllocWithZone -- this indicates whether there is an implementation of alloc / allocWithZone (only the class that does not inherit NSObject/NSProxy is true)
     */
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        // No alloc/allocWithZone implementation. Go straight to the allocator.
        // fixme store hasCustomAWZ in the non-meta class and 
        // add it to canAllocFast's summary
        /**
         Calling bits.canAllocFast internally, the default value is false. You can click it to view it
         */
        if (fastpath(cls->canAllocFast())) {
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor();
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            // Has ctor or raw isa or something. Use the slower path.
            id obj = class_createInstance(cls, 0);
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            return obj;
        }
    }
#endif

    // No shortcuts available.
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}

3. In the class ﹣ createinstance method, trace the class ﹣ createinstance (CLS, 0) method, which internally calls the ﹣ class ﹣ createinstancefromzone method, in which size calculation, memory application and isa initialization are performed

class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

_Class "createinstancefromzone" is the core method to open up memory and associated objects

_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if (!cls) return nil;

    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
   // Get the memory size to be opened
    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
        //Here is the real way to create a class, but the source code of calloc is in malloc
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        //Associating memory with objc
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}

CLS - > instancesize (extrabytes) this method is used to calculate how much memory should be opened to calculate the size,
In 64 bit system, the object size is aligned with 8 bytes, but the minimum applied memory is 16 bytes, that is to say, the system allocates memory aligned with 16 bytes

 size_t instanceSize(size_t extraBytes) {
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;//Minimum return 16 bytes
        return size;
  }

 uint32_t alignedInstanceSize() {
        //byte alignment
        return word_align(unalignedInstanceSize());
  }

#   Define word? Mask 7ul / / this macro marks the number 7
static inline uint32_t word_align(uint32_t x) {
    //WORD_MASK  7
    //7+8 = 15
    //0000 0111 2 decimal 7
    //0000 1111 binary representation 15
    //1111 1000 binary representation ~ 7
    //0000 1000 15 & ~ 7 value 16
    //This is mainly to do octal alignment, which is equivalent to 8 times
    //x + 7
    //x=1  8
    //x=2  16
    return (x + WORD_MASK) & ~WORD_MASK;
}

According to different conditions, use calloc or malloc ﹣ zone ﹣ calloc to apply for memory, and initialize isa pointer, so that obj of size size has been applied for and returned;

4. This is the way to call the bottom layer of alloc in OC. We have seen the source code of alloc. By the way, let's see the init method;

- (id)init {
    return _objc_rootInit(self);
}

id
_objc_rootInit(id obj)
{
    // In practice, it will be hard to rely on this function.
    // Many classes do not properly chain -init calls.
    return obj;
}

We can see that Apple developers didn't do anything in init method, just returned the object directly, indicating that this is just a factory method provided by Apple developers for us, which makes it easy for us to rewrite;

5. By the way, take a look at the New method

+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}

If you look at the source code of alloc carefully, it's clear here that callAlloc calls the bottom layer of the next alloc method and init by the way. That is to say, the bottom layer of the new method also calls alloc,

 

summary

This is the conclusion of personal actual tracking source code. There are some branch codes in the source code that can be viewed by yourself. If there are any mistakes, please correct them. We will discuss together. The development level is average. We hope you will understand.

Write to yourself: it's boring to study the source code, but don't be afraid to face the source code. Separate it step by step and study it. Take a look at the official comments / Github's comments. Slowly, there will be one who will be powerful.

Published 59 original articles, won praise 17, visited 20000+
Private letter follow

Posted by gibs on Tue, 03 Mar 2020 00:33:40 -0800