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

2021-07-23  本文已影响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这个版本。

+ (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路径比较长,这里不一一列出,实际上里面做了很多事情,后面单独分析。

- (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方法实际上什么也没做,返回的是对象本身,跟我们的推测是一致的。

+ (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方法调用流程。

屏幕快照 2020-09-11 上午10.57.12.png

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

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);
}
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指针的更多信息

上一篇下一篇

猜你喜欢

热点阅读