iOS底层探索之alloc&init

96 阅读3分钟

作为一名iOS开发者,alloc对我们来说是一个熟悉又陌生的单词。在代码的世界里,撩动着心弦。像极了公司楼下总擦肩而过的姑娘,温柔并且风情万种。但是你不知道她的名字,年龄,有没有对象...

西楼月下当时见
泪粉偷匀
歌罢还颦
恨隔炉烟看未真
别来楼外垂杨缕
几换青春
倦客红尘
长记楼中粉泪人

嗯,要勇敢。让我们来认识一下这位可爱的姑娘吧。

1. alloc是谁

Girl *girl = [[Girl alloc] init];
girl.name = @"铁锤妹妹";
NSLog(@"girl's name is %@", girl.name);
// girl's name is 铁锤妹妹

这是创建对象最常用的代码。allocinit总是成对出现。很显然,专一是一种优秀的品质。那不如把init先去掉。

Girl *girl = [Girl alloc];
girl.name = @"铁锤妹妹";
NSLog(@"girl's name is %@", girl.name);
// girl's name is 铁锤妹妹

从打印来看,有没有init好像并不影响对象的创建。我们可以拿到Girl对象并且可以访问对象的属性。

2. alloc做了什么

首先我们先把Always Show Disassembly打开。在Girl *girl = [Girl alloc];处加上一个断点。

显示堆栈信息.png

断点1.png

可以看到,当我们调用alloc时,会调用objc_alloc函数。

源码总是靠近真相最直接的武器。objc源码
关于objc源码的编译调试,相关文章很多,也有已编译好的代码。

源码1.png

通过加断点分析,我们可以得到函数的调用顺序如下:

alloc
objc_alloc
callAlloc
[NSObject alloc]
_objc_rootAlloc
callAlloc
_objc_rootAllocWithZone
_class_createInstanceFromZone

到这里我们可以明白,_class_createInstanceFromZone才是alloc真正做的事。

3. _class_createInstanceFromZone

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

// extraBytes == 0, zone == nil
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 {
        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);
}

这里需要注意的是size的计算,cls->instanceSize(extraBytes)是计算创建cls对象所需的字节大小,内部会做字节对齐处理。在64位操作系统是8字节对齐,32位操作系统4字节对齐。并且如果所计算的字节数小于16,会直接返回16字节。

字节对齐的相关代码:

inline size_t instanceSize(size_t extraBytes) const {
    // 先从cache中查找,有的话直接返回
    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;
}

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

#ifdef __LP64__
#   define WORD_SHIFT 3UL
#   define WORD_MASK 7UL
#   define WORD_BITS 64
#else
#   define WORD_SHIFT 2UL
#   define WORD_MASK 3UL
#   define WORD_BITS 32
#endif
// 字节对齐算法
static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

看到这里,我们是不是就可以认为在创建对象进行字节对齐的时候采取的是8字节对齐的方式呢。其实不然,在(id)calloc(1, size)方法中,会重新对内存进行对齐,这里采用16字节对齐的方式。
calloc的实现并不在objc源码中,我们需要去下载libmalloc的源码。

我们可以得到一个结论,那就是alloc在调用的时候就开辟了内存空间,并且返回了对象。那么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;
}

我们看到,在NSObject类的init方法中,什么都没有做。直接返回obj对象。就是为了给到我们去重写。
alloc方法是创建对象的关键,在底层做了很多的工作。后面我们来探究一下对象的本质又是什么。

待续...