iOS底层原理探究——alloc流程探索

281 阅读2分钟

疑问???

LGPerson *p1 = [LGPerson alloc];
LGPerson *p2 = [p1 init];
LGPerson *p3 = [p1 init];

NSLog(@"%@-%p-%p",p1,p1,&p1);
NSLog(@"%@-%p-%p",p2,p2,&p2);
NSLog(@"%@-%p-%p",p3,p3,&p3);

运行结果如下:

2021-06-07 00:46:42.553533+0800 alloc探索[26990:394582] <CJPerson: 0x6000023789d0>-0x6000023789d0-0x304e07078
2021-06-07 00:46:42.554203+0800 alloc探索[26990:394582] <CJPerson: 0x6000023789d0>-0x6000023789d0-0x304e07070
2021-06-07 00:46:42.554282+0800 alloc探索[26990:394582] <CJPerson: 0x6000023789d0>-0x6000023789d0-0x304e07068

由此结论可知:p1,p2,p3的内存地址都是相同的,都是指向的同一块区域。那么在底层allocinit分别都做了什么???

工欲善其事必先利其器

  • 从Apple开源网站下载objc的源码(注:这里使用的-objc4-818.2演示)。
  • 编译源码,可参考这篇文章

alloc源码初探

首先在可编译源码的main函数入口写一个简单的代码,然后打上断点(注:这里使用的-objc4-818.2)

WeChat085b9e1256c3c25b9038016bc3d97b33.png

然后通过 Step into 进入源码

首先我们看到的是alloc方法:

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

内部调用了_objc_rootAlloc()

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

进入callAlloc()

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

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

继续进入_class_createInstanceFromZone() (核心代码)

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;
    ///  计算需要开辟的内存空间大小(内部采用了16字节对齐)
    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) {
        /// 关联 (将isa -> class关联起来)
        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);
}

配合断点调试可以看到_class_createInstanceFromZone()方法里面主要有三个核心步骤:

  • size = cls->instanceSize(extraBytes);计算大小(内部采用了16字节对齐方式)
  • obj = (id)calloc(1, size); 开辟空间
  • obj->initInstanceIsa(cls, hasCxxDtor); 将isa和cls进行关联

总结

直接附上alloc内部执行流程图:

alloc底层源码探究.png