美文网首页
OC对象创建过程 - alloc、init & new

OC对象创建过程 - alloc、init & new

作者: 希尔罗斯沃德_董 | 来源:发表于2021-07-23 16:34 被阅读0次

    什么是 alloc 、init 和 new?

    先来看看我们经常初始化一个对象的方法
    NSobject *object1 = [[NSobject alloc] init]; 这里面其实是调用了两个方法,类方法alloc 和 对象方法init。那这里就有个疑问:alloc和init分别做什么呢?苹果为什么会这么设计呢?针对这个问题,通过以下的demo来进行探究。

        NSObject *obj1 = [NSObject alloc];
        NSObject *obj2 = [obj1 init];
        NSObject *obj3 = [obj1 init];
        LGNSLog(@"%@ - %p - %p",obj1,obj1,&obj1);
        LGNSLog(@"%@ - %p - %p",obj2,obj2,&obj2);
        LGNSLog(@"%@ - %p - %p",obj3,obj3,&obj3);
    

    打印结果
    <NSObject: 0x6000036a4380> - 0x6000036a4380 - 0x7ffee7fc9158
    <NSObject: 0x6000036a4380> - 0x6000036a4380 - 0x7ffee7fc9150
    <NSObject: 0x6000036a4380> - 0x6000036a4380 - 0x7ffee7fc9148

    有这个打印结果可以发现obj1、obj2和obj3指向的内存地址以及他们的类型是一样的,不一样的还是变量本事的地址。因此我们可以推测,对象在alloc方法中就已经分配了内存地址和类信息的绑定。而init方法则不会改变对象的内存地址。实际上init是苹果为了方便开发者统一对象初始化方法而设计的工厂方法,开发者可以根据需要重写init方法做自己定义的初始化操作。

    为了进一步验证我们的推测,可以直接阅读苹果官方源码:官方源码地址,这里面很多版本,我选用的781这个版本。

    • alloc
    + (id)alloc {
        return _objc_rootAlloc(self);
    }
    // Base class implementation of +alloc. cls is not nil.
    // Calls [cls allocWithZone:nil].
    id
    _objc_rootAlloc(Class cls)
    {
        return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
    }
    
    

    可以看出alloc方法通过先调用_objc_rootAlloc函数,并通过_objc_rootAlloc调用callAlloc函数实现了包括内存分配和类型绑定的操作,跟我们的推测是一致的。callAlloc路径比较长,这里不一一列出,实际上里面做了很多事情,后面单独分析。

    • init
      接下来通过源码分析init方法:
    - (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;
    }
    

    在这里我们可以看到init方法实际上什么也没做,返回的是对象本身,跟我们的推测是一致的。

    • new
      接下来我们看看new方法:
    + (id)new {
        return [callAlloc(self, false/*checkNil*/) init];
    }
    

    这里我们看到new方法实际上也是直接调用的callAlloc函数和init方法进行初始化的。那么简化成OC代码其实 [NSObject new]等价于[[NSObject alloc] init]的。但是我们为什么不推荐使用new方法呢?主要还是为了防止有些对象自定义了初始化方法,而new方法内部只是简单的调用了init方法,这样就有可能会漏掉一些初始化信息;而且从代码设计角度讲也不利于代码风格的统一。

    alloc到底做了什么,怎么做?

    由前面的分析我们可以知道init、new方法都很容易理解,但是alloc调用流程却相对复杂,那么alloc方法除了内存分配和类绑定还做了哪些事情?对象内存是如何分配的?以及如何进行类型绑定的?接下来我们继续对源码进行分析,了解alloc方法调用流程。

    • alloc流程图
      根据源码的分析,我们来大概绘制一下alloc方法调用的流程图:
    屏幕快照 2020-09-11 上午10.57.12.png

    可以说alloc流程还是比较复杂的,这里我们对一些比较重要的函数进行分析。

    • callAlloc
      有源码可知alloc内部实际上是先调用_objc_rootAlloc函数,然后_objc_rootAlloc只是调用callAlloc函数并返回。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));
    }
    
    

    以上代码可以看出callAlloc里面会调用_objc_rootAllocWithZone函数,这个函数实际上只是直接调用_class_createInstanceFromZone并返回,代码如下:

    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);
    }
    
    • _class_createInstanceFromZone
      在这个过程我们发现创建对象的核心函数是_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 {
            // alloc 开辟内存的地方
            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);
    }
    
    

    分析代码发现里面主要调用有三个函数instanceSize、calloc和initInstanceIsa。instanceSize用来计算对象的大小size:

        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;
        }
    

    随后calloc根据size开辟出一个内存空间并返回内存地址,这个内存是16字节对齐的(关于内存对齐参考内存对齐),它是对象的地址:

    obj = (id)calloc(1, size);
    

    这时候的obj还没有指向任何类型,就是一个普通的指针。获得生成的对象obj之后会调用initInstanceIsa(或initIsa)函数进行对象和类的绑定,实际上不管是调用initInstanceIsa或initIsa,它们最后都会调用objc_object::initIsa方法:

    inline void 
    objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
    { 
        ASSERT(!isTaggedPointer()); 
        
        const char *mangledName = cls->mangledName();
        if (strcmp("MyObject", mangledName) == 0) {
            if(!cls->isMetaClass()){//避免元类的影响
                printf("我来了 MyObject");//定位要调试的类
            }
        }
        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;
    }
    
    

    通过代码可以就看出这个函数实际上就是设置isa指针,让isa指针指向对象的class信息以及初始化引用计数为1(newisa.extra_rc = 1)。点击了解isa指针的更多信息

    相关文章

      网友评论

          本文标题:OC对象创建过程 - alloc、init & new

          本文链接:https://www.haomeiwen.com/subject/jystektx.html