美文网首页
iOS开发中的 alloc 的执行流程学习总结

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

作者: 携YOU手同行 | 来源:发表于2020-09-07 11:54 被阅读0次

    一、序言

    在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 的执行流程学习总结

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