美文网首页
iOS底层系列03 -- alloc init new方法的探索

iOS底层系列03 -- alloc init new方法的探索

作者: YanZi_33 | 来源:发表于2021-02-04 09:55 被阅读0次

    探索的准备工作

    • 准备好objc-781编译成功的源码工程;
    • 新建调试Target -> YYTest;
    • 在YYTest的main函数中,调用alloc方法创建一个NSObject实例,并打下断点;
    Snip20210624_1.png

    开始调试探索NSObject的alloc底层实现

    1. 首先会调用objc_alloc方法;
    id objc_alloc(Class cls){
        return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
    }
    
    2.内部调用的是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;
        //判断当前类是否有自定义的+allocWithZone实现.
        //默认是实现了allocWithZone方法
        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与编译器的优化相关,其可以提高编译器的工作效率,后面再做详细阐述;
    • !cls->ISA()->hasCustomAWZ():判断当前类的cache_t中是否存在alloc/allocWithZone方法;
    bool hasCustomAWZ() const {
            return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
    }
    
    • 其中#define FAST_CACHE_HAS_DEFAULT_AWZ (1<<14) 表示当前类,或者其父类是否有alloc/allocWithZone方法,
    • 由于是第一次调用alloc方法,所以缓存中不存在alloc方法,但NSObject是系统类,缓存中存在,自定义类缓存中是不存在的;
    3. 接着调用 _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);
    }
    
    4. 调用 _objc_rootAllocWithZone函数,内部调用 _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;
        //2.为实例对象申请内存空间并返回地址指针
        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;
        }
    
        //3.为实例对象创建一个isa,并将isa与其所属的类class进行绑定
        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);
    }
    
    • _class_createInstanceFromZone是实例化对象的核心函数,其内部实现包含三个主要步骤:
    • 第一步:计算实例对象所占内存大小(instanceSize);
    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;
    }
    
    • instanceSize()的函数实现:

      • 首先去缓存cache_t中去获取实例对象的内存大小,调用cache.hasFastInstanceSize()判断能否从缓存中快速获取内存大小,若可以则调用cache.fastInstanceSize(),返回内存大小;
      • 缓存未命中,调用alignedInstanceSize()函数去获取内存大小;
      • 若对象的内存大小 小于16字节,则修正为16字节,表明一个OC对象至少会占用16个字节的内存空间
    • 经调试主流程进入第一步,调用cache.fastInstanceSize()去获取实例对象的内存大小;

    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字节对齐表明OC对象的内存大小的计算采用的是16字节对齐的算法
    static inline size_t align16(size_t x) {
        return (x + size_t(15)) & ~size_t(15);
    }
    
    • 未进入缓存时的逻辑,会调用alignedInstanceSize()函数其实现如下:
    uint32_t alignedInstanceSize() const {
        return word_align(unalignedInstanceSize());
    }
    
    define WORD_MASK 7UL
    static inline uint32_t word_align(uint32_t x) {
        return (x + WORD_MASK) & ~WORD_MASK;
    }
    
    • 可以看出此函数使用位运算 算法完成 8字节对齐

    • 第二步:为实例对象申请内存空间并返回地址指针
    if (zone) {
       obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
       obj = (id)calloc(1, size);
    }
    
    • 主流程走的是 obj = (id)calloc(1, size) 调用calloc函数申请内存空间,并返回地址指针,从 iOS底层系列08 -- malloc与calloc源码分析,可知计算实例对象分配的内存空间采用的是16字节对齐算法;

    • 第三步:为实例对象创建一个isa,并将isa与其所属的类class进行绑定
    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);
    }
    
    • 主流程走obj->initInstanceIsa(cls, hasCxxDtor) 其内部实现如下:
    inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
    {
        ASSERT(!cls->instancesRequireRawIsa());
        ASSERT(hasCxxDtor == cls->hasCxxDtor());
    
        initIsa(cls, true, hasCxxDtor);
    }
    
    • 最后来到initIsa(cls, true, hasCxxDtor),其创建了isa_t对象然后与class建立联系,最后赋值给实例对象的isa属性;
    inline void 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对象
            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;
            //isa_t属性与class的绑定关系建立
            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
            // This write must be performed in a single store in some cases
            // (for example when realizing a class because other threads
            // may simultaneously try to use the class).
            // fixme use atomics here to guarantee single-store and to
            // guarantee memory order w.r.t. the class index table
            // ...but not too atomic because we don't want to hurt instantiation
            //将创建isa_t赋值给对象的isa属性,实现了对象isa的初始化,即实现了对象与class的绑定
            isa = newisa;
        }
    }
    
    • 上述探索的是objc_781版本 [NSObject alloc]底层执行逻辑,后来又研究了objc_818版本的源码,存在一些差异,下面是objc_818版本 [NSObject alloc] 的底层实现流程图:
    NSObject_alloc.png
    计算实例对象的内存大小的逻辑总结
    • 首先在计算实例对象实际所占内存空间存在两个分支逻辑:
      • 第一个分支:直接从cache_t中获取,采用的是16字节对齐算法;
      • 第二个分支:调用alignedInstanceSize()方法,采用的是8字节对齐算法;
    • 其次是根据实例对象实际所占内存空间大小,去计算实例对象分配的内存空间大小,调用calloc函数,采用的是16字节对齐算法;

    探索init的底层实现

    • 源码如下所示:
    // Replaced by CF (throws an NSException)
    + (id)init {
        return (id)self;
    }
    
    - (id)init {
        return _objc_rootInit(self);
    }
    
    • 类方法,直接返回本身;
    • 实例方法,内部调用了_objc_rootInit(self)函数;
    id _objc_rootInit(id obj)
    {
        // In practice, it will be hard to rely on this function.
        // Many classes do not properly chain -init calls.
        return obj;
    }
    
    • 也是返回其自身;
    • 从这里可看出alloc方法计算对象内存大小,分配内存创建实例对象,实例对象绑定类关系完成了大部分工作,而init方法仅仅只是返回实例对象本身而已

    探索new的底层实现

    • 源码如下所示:
    + (id)new {
        return [callAlloc(self, false/*checkNil*/) init];
    }
    
    • 看到new方法内部调用了alloc底层方法+init方法,所以new = alloc + init

    图解16字节对齐算法

    • 代码实现如下:
    static inline size_t align16(size_t x) {
        return (x + size_t(15)) & ~size_t(15);
    }
    
    16字节对齐.png

    相关文章

      网友评论

          本文标题:iOS底层系列03 -- alloc init new方法的探索

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