美文网首页
OC底层-对象的alloc流程探究

OC底层-对象的alloc流程探究

作者: 浅笑慕 | 来源:发表于2020-09-07 13:34 被阅读0次

准备工作

开始探究

创建对象:


添加断点,ctrl+strp into进入后,可以看到alloc实际上调用了objc_alloc函数

继续向下分析,就用到了官方下载的源码objc4-781,源码下载后再进行debug调试的时候,也是花了很久的功夫,这里就不详细说明。有关编译和调试的问题可以留言或私信

来到源码中,我们可以很快定位到alloc的上层代码如下

+ (id)alloc {
    return _objc_rootAlloc(self);
}
id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

找到第一个核心方法callAlloc,代码如下

#define ALWAYS_INLINE inline __attribute__((always_inline))
#define NEVER_INLINE __attribute__((noinline))

#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))

static ALWAYS_INLINE id
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));
} 

这里想要继续往下,我们只有添加断点通过调试才能确定判断下一步,笔者经过断点确定了是_objc_rootAllocWithZone,跟_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 {
        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);
}

这个时候,我们已经接近真相了,alloc究竟为我们做了什么

*initInstanceIsa 计算申请的内存大小
*calloc申请内存地址指针
*initInstanceIsa 将申请的内存地址指针和isa指针即我们的类绑定起来

alloc流程图.png

探究过程的细节思考和知识联想

在学习知识的时候,我们往往都是碎片化的理解和记忆,不妨每次给自己几分钟做一些思考和联想,让知识串联起来。如果文章中出现细节错误,可以留言指出来或者私信给我。思考出错不可怕,可怕的是我们思考了吗?

1,关于callAlloc方法中的条件判断,slowpathfastpath,这里应该是编译器的优化,我们在项目target > Build Setting > Apple Clang - Code Generation可以发现其踪迹。
2,关于initInstanceIsa中计算申请的内存大小,我们可以通过调试得知申请的地址都是16的倍数,这里字节对齐的单元是16,至于为什么要字节对齐,应该是为了降低cpu的读取性能损耗和访问的安全性。给类加了属性,利用lldb中x/4gx来打印类的地址,又可以发现申请的内存大小跟类的属性关联。
3,init 和 new ,在源码中init做的事情是return _objc_rootInit(self),init把我们alloc 申请的类的指针给了对象,并且返回对象自己。init构造方法,并且可以重写。new做的事情是return [callAlloc(self, false/*checkNil*/) init],new就相当于 alloc+init,但是new不能像init一样进行重写。

相关文章

网友评论

      本文标题:OC底层-对象的alloc流程探究

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