iOS开发中的 alloc 的执行流程学习总结

一、序言

在iOS开发过程中的几年中,很多东西都没有静下来好好研究一下,就如一些简单的东西,而是只是按部就班的知道大家都是这样使用的,所以也就囫囵吞枣的使用着,具体是如何实现的,究竟使用了一些什么东西,自己也是一头雾水,如今进入直播课堂,根据课堂布置的作业,自己才有机会去慢慢研究一些简单的底层实现,

比如就连我们经常使用的创建一个对象的简单的不能再简单的事,如果要真的问,

  • alloc 底层都做了什么?
  • 内存分配是怎么样的?
  • 怎么就能创建一个你想要的类对象示例?
  • 在内存分配后如何关联你创建的对象的?

这一系列的问题对我自己来说肯定是一问三不知,所以今天自己去结合项目和老师讲解的内容,自行整理一些知识点,大神略过。本文是自我学习总结的一个过程,而不是为了技术分享。希望在茫茫的知识海洋中追求自己想要的一点小进步。

二,alloc 分配过程都做了些什么,

我们利用项目中的创建一般对象,断点调试能发现,


图片.png


通过一系列的符号断点调试,我们能发现alloc 底层的调用过程有

  • _objc_rootAlloc
  • calloc
  • objc_msgSend
  • _objc_rootAllocWithZone
  • _class_createInstanceFromZone

其中 _class_createInstanceFromZone 又分为三步

1, cls ->InstanceSize //计算所需内存大小
2, calloc, //,根据所需内存大小进行系统分配
3,objc ->InitInstanceIsa //分配了内存后和相关的类进行关联对象

大致流程图如下,图片来源于逻辑教育金牌讲师---那个最帅的男人Cooci老师PPT 截图

图片.png

接下来我们逐一的进行源码分析,解析alloc 的分配流程:

step1:_objc_rootAlloc

首先根据断点进入到alloc 的断点,我们进入到的代码是

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

在此step into ,能看到我们的第一步调用的是 _objc_rootAlloc(self);我们再次进入源码到

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

第一步我们看到的只是一个简单的调用系统方法,没有太多的问题,所以相信大家都明白

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

第二步主要是进行条件的判断,当然最下边的那部分判断一般情况下是不会走的,所以也没什么大的作用,因为allocWithZone这是苹果早起的内存分配情况,现在都不怎么使用此方法进行内存分配了,除非系统比较老的版本可能有用。

首先进行判空处理,在判断类对象是否为空,如果成立,则直接返回空,不进行创建,否则判断类对象的ISA对象内存分配有无情况

bool hasCustomAWZ() const {
        return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
    }

如果没有,分配,才调用step3,_objc_rootAllocWithZone(cls, nil);

objc_msgSend 再此处的调用是转换为系统的一系列方法调用,详细了解RunTime 的开发者都知道在系统的任何调用方法,都会转换为底层的方法转发,通过ISA找到相应的函数实现IMP,所以此处并非是一个具体的方法,而是一系列的系统函数调用,所以没有断点进入到具体的某一个IMP。

step3: _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);
}

第三步直接调用了step4的方法,也是alloc最重要的逻辑存在;

step4:_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;
    1:根据字节对齐机制计算出相关的分配所需内存空间的大小
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
       2 : 根据第一步计算所需空间,向系统开辟内存
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
        
         3:根据分配的内存情况管理对象
            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);
}

次步骤就是iOS的对象创建核心内容,其中包括了三个步骤,我已经在代码中标注了1、2、3的地方,接下来我们对里边的具体实现进行一个详细的分析。我们顺着代码进入到第一步

  • 步骤1:计算相关内存大小
 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;
    }

根据断点我们能进入到 return cache.fastInstanceSize(extraBytes);这里,再次进入代码到

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

最后通过一个计算得到是一个align16的数据返回类型,第一段代码告诉我们,系统是通过16字节对齐的方式,如果分配小于16字节,就直接等于16字节。所以此时的size 是16,
align16(size + extra - FAST_CACHE_ALLOC_DELTA16);

  • size : 16字节对齐,此时是16,
  • extra :是系统计算的额外信息,根据断点此时是0,
  • FAST_CACHE_ALLOC_DELTA16:是宏定义 :此时是8


    图片.png

所以结合最终返回的align16的值是 align16(8)

我们在此进入align16的定义函数

static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}

此处代码的意思就是,上一步计算结果 的值加上15,和15的值取反过后再进行 按位与操作,
什么意思呢:例如上一步我们计算的结果是8,再加上15 结果等于23,23在内存中的标识是这样的

0000 0000 0001 0111

15的内存表示

0000 0000 0000 1111

15取反操作的值是

1111 1111 1111 0000

所以按位与的操作就是 23 & ~15

0000 0000 0001 0111
1111 1111 1111 0000
0000 0000 0001 0000(结果)十进制的16

如果是内存分配大于16 的,比如 20 加上15表示成 35,内存表示(0000 0000 0010 0011)
按位与操作是 35 & ~15

0000 0000 0010 0011
1111 1111 1111 0000
0000 0000 0010 0000(结果) 十进制的32,

所以这就是16字节对齐的核心机制,相信我已经介绍的够清晰了,这就是iOS内存分配的最新机制。

  • 步骤2 :分配内存

根据计算的内存size_t size 进行内存的分配,


图片.png
  • 步骤3 关联对象类型

通过InitInstanceIsa 方法进行关联类对象实例,

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

    initIsa(cls, true, hasCxxDtor);
}
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa = isa_t((uintptr_t)cls);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());

        isa_t newisa(0);

#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;

        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif

        isa = newisa;
    }
}

这就是 alloc 的完整流程;
通过ISA的关联,我们就完成了一个对象的内存分配情况,也就是一个对象的alloc 的完整流程,因为任何一个类对象都可以通过ISA指针找到相应的父类,以及元类,这样对象的各种初始化实例方法都能通过ISA指针指向找到相应的IMP。从而调用方法进行消息转发。

三:总结

以上内容是个人经过老师指导,视频的回看,加上个人的整理,有什么不足的地方,还请各位大神多多指教,我一定会努力修正自己的错误。这就是我个人对alloc的流程分析。

你可能感兴趣的:(iOS开发中的 alloc 的执行流程学习总结)