iOS开发 — OC对象创建流程及源码分析

333 阅读5分钟

在平常的开发中使用[[NSObject alloc] init]去创建一个对象是再正常不过的事,但是alloc和init底层分别做了什么事情却很少去研究,抱着这样的疑问我查阅了相关资料和objc的源码。

首先我们需要从苹果官方开源代码列表找到需要查看的源码,这里我用到的是最新的objc-756.2版本。下载到本地后需要进行编译调试才能使用的,这里我参考了Cooci大神的iOS_objc4-756.2 最新源码编译调试

1.alloc源码分析

源码调试完毕后就可以开始分析流程了

首先搜索alloc { ,注意这里是花括号,这样我们得到结果

点击后看到代码

继续跟进去

再点进去可以看到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];
}

这个方法里其实有很多点要说,不过这次重点是alloc创建对象的流程,其他的之后再补充。

我们在这里下一个断点



然后跟进去



再跟进去可以看到_class_createInstanceFreomZone()方法的源码:

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());

    // 一次读取类的信息位以提高性能
    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) {
        // 分配1块大小为size的连续内存
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        // 初始化对象的isa
        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;
}

因为最后一句 return obj; 我们可以得知obj就是最后返回的创建好的对象,那么obj在何时创建的呢,我们先在这里下个断点:

跟着断点往下走,在6524行和6526行分别 po obj 可以得到结果:

由此我们可以知道calloc()函数为对象开辟了一块大小为size的连续内存空间,而initInstanceIsa()函数通过初始化isa将对象和当前的class关联起来。简化来说就是这两行代码创建了对象。那么此时还有其他问题,内存空间的大小size是如何确定的呢?还有这个initInstanceIsa()函数里面到底做了什么?

我们先来看size的问题

从_class_createInstanceFreomZone()方法的源码中可以看到

size_t size = cls->instanceSize(extraBytes);

这说明size的大小是由instanceSize()函数确定的,点进去查看源码:

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

此时extraBytes是0,暂时可以不用管,这里可以看到即使size<16,也会开辟一块大小为16字节的内存,继续查看alignedInstanceSize()的源码:

// Class’s ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
    return word_align(unalignedInstanceSize());
}

由函数名可以得知word_align()的作用是字节对齐,先点进unalignedInstanceSize()函数里查看源码:

// May be unaligned depending on class’s ivars.    
uint32_t unalignedInstanceSize() {        
    assert(isRealized());        
    return data()->ro->instanceSize;
}

这里作用其实就是从类的data数据段的ro里拿到数据信息,至于ro是什么会在之后研究,现在只知道这里会返回一个uint32_t类型的size,然后再查看word_align()的源码:

static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}
static inline size_t word_align(size_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

这里用了一个很巧妙的算法,WORD_MASK在64位操作系统下是7,通过计算可以知道word_align()函数的作用就是8字节对齐。

通过这一系列的分析我们可以知道size的大小是8的倍数并且最小是16。

因为isa是很重要的一点,所以initInstanceIsa()函数里面具体做了什么还有isa的结构等等博主会新起一篇文章来记录研究过程。

从上面的代码和分析中可以看到alloc大体上做了很多事情,并且最终返回了创建的对象,那么问题来了,init究竟做了什么事情呢?

2.init和new的作用

首先查看init的源码:

// Replaced by CF (throws an NSException)
+ (id)init {
    return (id)self;
}

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

我们可以看到,无论是类方法还是对象方法init所做的都只是将alloc创建的对象返回。那么苹果官方为什么这么设计呢?其实也不难理解,这是一种工厂设计模式,为了开发者在日常的开发中根据不通的业务需求来重写init。

再来看看new:

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

显然,new相当于 alloc + init 。

3.总结

我们通过最常见的alloc函数源码分析出了对象创建的大体流程,下面用一张流程图总结一下alloc创建对象的过程:


其实这之中还有很多没分析到的和不足的地方,我会在以后逐渐补充进来。