iOS底层原理1——alloc初探

前言

作为一名iOS开发者,想要进阶,底层原理是必经之路,也是前往大厂的筹码之一。接下来,我将从最基础的代码开始,探索iOS底层原理。那么如何探索呢?我们今天就从alloc开始征程。

alloc对象的指针地址和内存

    XKPerson *objc1 = [XKPerson alloc];
    XKPerson *objc2 = [objc1 init];
    XKPerson *objc3 = [objc1 init];

    NSLog(@"%@ - %p - %p",objc1,objc1,&objc1);
    NSLog(@"%@ - %p - %p",objc2,objc2,&objc2);
    NSLog(@"%@ - %p - %p",objc3,objc3,&objc3);

打印结果:

alloc&init初探[5783:115470]  - 0x6000030406b0 - 0x16f16ff28
alloc&init初探[5783:115470]  - 0x6000030406b0 - 0x16f16ff20
alloc&init初探[5783:115470]  - 0x6000030406b0 - 0x16f16ff18

根据打印结果,我们可以得出以下结论:

  • obj1obj2obj3 打印的对象及其地址是一样的,但是指针地址是不一样的,这三个指针指向了同一块内存空间
  • alloc会创建一块内存
  • init不会对当前的指针做任何操作
总结:
  • alloc可以开辟出一块内存空间,而init不能开启出内存空间。
  • 堆区的内存地址是从低地址到高地址, 栈区的内存地址是从高地址到低地址。这样堆区和栈区内存就不会有具体的大小分界线,有利于堆区和栈区内存大小的动态变化。

源码准备工作

  1. 下载 苹果官方:objc4-818.2 源码(PS: 目前824无法下载),编译可以思路可参考 源码编译调试
  2. 编译好的源码 gitHub:objc4-818.2(PS:如果是M1电脑请注释下图两处代码:)
    第一处请注释

    第二处请注释
  3. 这里给大家提供一下苹果官方源码库,大家一定不要放过它

alloc源码初探

这里以objc4-818.2版本的源码为例进行探索,接下来请大家集中注意力,认真观看,现在开始……

废话不都说先给大家来一张alloc底层源码流程图:

alloc底层探索流程图.png

接下来我们根据这张流程图,一步一步开始解析:

第一步:在main函数里面写下NSObject *objc = [NSObject alloc]; 并进入 alloc中探索

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *objc = [NSObject alloc];
    }
    return 0;
}

第二步:进入_objc_rootAlloc中探索

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

第三步:进入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*/);
}

第四步:这里就到了核心方法:_objc_rootAllocWithZone,这个方法就是在开辟内存空间,下面继续探索如何开辟内存空间的,继续往下看……

// 这里是核心方法
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
    //  传入的checkNil为nil, !cls为nil, 所以 slowpath为false
    if (slowpath(checkNil && !cls)) return nil;

    // 判断类或父类是否有alloc/allocWithZone的默认实现
    // fastpath为真值判断
    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));
}
  • 注解一:slowpathfastpath
// __builtin_expect(bool(x), 1) 表示x很有可能为真
#define fastpath(x) (__builtin_expect(bool(x), 1))
// __builtin_expect(bool(x), 0) 表示x很有可能为假
#define slowpath(x) (__builtin_expect(bool(x), 0))
  • 注解二:hasCustomAWZ()
bool hasCustomAWZ() const {
        return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
// class or superclass has default alloc/allocWithZone: implementation(类或父类是否有alloc/allocWithZone的默认实现)
// Note this is is stored in the metaclass.
#define FAST_CACHE_HAS_DEFAULT_AWZ    (1<<14)
    }

第五步:进入_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);
}

第六步:这里就是重中之重了,认真看,这里有三句重要代码分别是:size = cls->instanceSize(extraBytes);obj = (id)calloc(1, size);obj->initInstanceIsa(cls, hasCxxDtor);

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);计算需要的内存大小
inline size_t instanceSize(size_t extraBytes) const {
        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;
    }
//  objc2基本可以快速实例 
bool hasFastInstanceSize(size_t extra) const
    {
        if (__builtin_constant_p(extra) && extra == 0) {
            return _flags & FAST_CACHE_ALLOC_MASK16;
        }
        return _flags & FAST_CACHE_ALLOC_MASK;
    }
//  快速开辟内存空间,最少16个字节,为什么最少要16个字节且是16字节对齐呢?
//  苹果采取16字节对齐,是因为OC的对象中,至少会有一个叫isa指针,它是必然存在的,而且它就占了8字节,即使对象中没有其它的属性了,那对象就至少要占用8字节。
//  如果是8字节对齐,那么如果有两块连续的对象内存都没有任何属性,只有一个isa,那么这两个对象的内存空间就会完全的挨在一起,容易产生错乱,为了尽量避免这种问题同时不浪费内存空间,就使用了16字节对齐,这样就能保证CPU的高效读取,达到以空间换时间的效果。
size_t fastInstanceSize(size_t extra) const
    {
        ASSERT(hasFastInstanceSize(extra));

        if (__builtin_constant_p(extra) && extra == 0) {
            return _flags & FAST_CACHE_ALLOC_MASK16;
        } else {
            size_t size = _flags & FAST_CACHE_ALLOC_MASK;
            // remove the FAST_CACHE_ALLOC_DELTA16 that was added
            // by setFastInstanceSize
            return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
        }
    }
//  16字节对齐
static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}
  • obj = (id)calloc(1, size);;根据内存空间大小向系统申请内存地址指针
    通过 instanceSize方法计算出所需的内存空间大小,然后向系统申请这个大小的内存,返回给obj,因此obj是指向内存地址的指针,注意,在没有给obj分配内存地址前,它里面是保存了一个地址,不过这是一个脏地址。

    第一次打印的是obj脏地址,第二次是开辟的内存空间的地址

  • obj->initInstanceIsa(cls, hasCxxDtor);将类与内存地址进行关联

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

到这里alloc的初探就告一段落,精彩仍将继续,请持续关注。

你可能感兴趣的:(iOS底层原理1——alloc初探)