IOS开发知识点

iOS进阶专项分析(一)、alloc和init的底层实现

2020-06-18  本文已影响0人  溪浣双鲤

引子:

一个经典的面试问题:

Objective-C中alloc和init的区别是什么?

或者是问:下面这块代码执行后打印的结果

BMPerson * person = [BMPerson alloc];
BMPerson * p1 = [person init];
BMPerson * p2 = [person init];
    
NSLog(@"\n打印地址 %p - %p - %p", person, p1, p2);

执行一下结果发现这三个对象的地址是相同的

2020-06-17 15:47:20.525517+0800 TextProject[12922:914030] 
打印地址 0x6000004b8680 - 0x6000004b8680 - 0x6000004b8680

为什么呢?接下来我来详细分析一下alloc和init底层究竟干了些什么

一、alloc底层探索

点击alloc方法在NSObject.h文件中只能看到方法声明,从这个声明我们能看出 alloc这个方法是一个类方法,会返回一个 instancetype 类型的值。但是看不到具体信息了

+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");

想要看alloc底部具体实现,我们需要去苹果官方文档open source里面下载 objc开放的源码 苹果开放文档传送门

下载完成打开objc4-750部分,直接搜索 alloc { 就能找到实现部分 (注意搜索输入的alloc{之间有空格)。

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

发现了一个方法 _objc_rootAlloc ,继续点击该方法定义,找到 callAlloc

// 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*/);
}

继续点击callAlloc,发现该方法的实现代码

// Call [cls alloc] or [cls allocWithZone:nil], with appropriate 
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    if (slowpath(checkNil && !cls)) return nil;

#if __OBJC2__
    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
        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];
}

分析这部分代码,抛开优化的部分,我们不难挑出核心代码

id obj = class_createInstance(cls, 0);
return obj;
            

继续进入方法 class_createInstance查看方法实现

static __attribute__((always_inline)) 
id
_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();

    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        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;
}


我们发现 _class_createInstanceFromZone里面 调用了cls->instanceSize calloc 以及 obj->initIsa , 即 开辟空间以及初始化isa, 至此 obj 初始化完成,直接把 obj 返回出去。

到这里alloc的流程已经比较清楚了,alloc通过我们的类创建了实例对象

二、init底层探索

那么问题来了,既然alloc都已经把实例变量创建好了,为什么还需要再调用init呢?
我们接下来在objc750源码中搜索 init {,发现了init这个实例方法调用了 _objc_rootInit 这个函数。

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

分析一下这里的调用,加上我们平时创建实例都是类似于 [[BMPerson alloc]init],所以此时的self就是我们在alloc里已经创建完成的实例对象,既然知道了此时的 self 是谁了,那么接下来我们继续进入 _objc_rootInit 这个函数,

id
_objc_rootInit(id obj)
{
    // In practice, it will be hard to rely on this function.
    // 实际上,将很难依靠此功能。 
    // Many classes do not properly chain -init calls.
    // 许多类没有属性 - 我们需要在init中按照业务需求自定义实现。
    return obj;
}

进入之后我们发现,_objc_rootInit这个方法里面其实什么也没做,直接把传入的self给返回了出去。接下来我们看注释,发现了苹果设计时候的用意。init 就是苹果提供给我们自定义的。这里体现了一个工厂设计模式父类没有执行,提供给子类自定义

三、总结

1、alloc&init底层调用流程图:

alloc&init底层调用.png

2、alloc方法底层已经完成开辟空间和创建类的实例变量了,init是专门为NSObject预留出来的,体现出工厂设计模式,即父类没有执行,提供给子类自定义

溪浣双鲤的技术摸爬滚打之路

上一篇下一篇

猜你喜欢

热点阅读