美文网首页
iOS 底层原理 alloc&init&new

iOS 底层原理 alloc&init&new

作者: jokerlee | 来源:发表于2020-09-09 07:31 被阅读0次

    前言

    alloc&init&new都分别做了什么
    我们在开发的过程当中 知道是通过这些方法初始化实例 但是并没有关注或研究过他们内部是怎么实现的

    准备工具

    苹果开源库

    正文

    首先来看一段代码

    Person *p1 = [Person alloc];
    Person *p2 = [p1 init];
    Person *p3 = [p1 init];
        
    NSLog(@"%@-%p-%p",p1,p1,&p1);
    NSLog(@"%@-%p-%p",p2,p2,&p2);
    NSLog(@"%@-%p-%p",p3,p3,&p3);
    

    打印的东西为对象,指针地址,对象地址
    输出结果

    15995807103349.jpg

    可以发现输出对象的地址是一样
    证明他们指向的是同一块内存地址

    alloc原理

    接下来我们通过断点去查看具体实现


    15995814805191.jpg

    接下来通过


    15995812344464.jpg

    输入alloc


    15995815122073.jpg

    点击下一个


    15995815395519.jpg

    确定到调用的那个库


    15995815980761.jpg

    调用的这个方法
    _objc_rootAlloc
    好我们接下来去下载对应的源码

    接下我们一步一步的分析

    • 1.alloc
      这个我们可以看到里边就调用了一个_objc_rootAlloc方法
      + (id)alloc {
        return _objc_rootAlloc(self);
        }
    
    • 2.跳转至_objc_rootAlloc
      其中调用callAlloc
    id
    _objc_rootAlloc(Class cls)
    {
        return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
    }
    
    • 3.跳转至callAlloc
      其中关于 slowpath & fastpath 这里简要说明一下 其实他们都是objc源码当中定义的宏
    /////x很可能为真, fastpath 可以简称为 
    真值判断 其实就是表示这个判断很可能为真 或者表达式成立 
    #define fastpath(x) (__builtin_expect(bool(x), 1))
    ///x很可能为假,slowpath 可以简称为
    假值判断 其实就是表示这个判断很可能为假 或者表达式不成立
    #define slowpath(x) (__builtin_expect(bool(x), 0))
    
    // Call [cls alloc] or [cls allocWithZone:nil], with appropriate 
    // shortcutting optimizations.
    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));
    }
    

    其中cls->ISA()->hasCustomAWZ()
    是判断一个类是否+allocWithZone实现
    因为这里是模拟器调试所以会执行_objc_rootAllocWithZone

    • 5跳转至_objc_rootAllocWithZone
      NEVER_INLINE
    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);
    }
    
    • 6跳转至_class_createInstanceFromZone实现
    /***********************************************************************
    * class_createInstance
    * fixme
    * Locking: none
    *
    * Note: this function has been carefully written so that the fastpath
    * takes no branch.
    **********************************************************************/
    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) {
                return _objc_callBadAllocHandler(cls);
            }
            return nil;
        }
    
        // 3: ?
        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);
    }
    

    该方法总结为3部分
    1.cls->instanceSize(extraBytes)计算要开辟的内存 以16字节对其
    2.(id)calloc(1, size) 开辟内存
    3.obj->initIsa(cls) isa与类关联 初始化isa

    根据源码总结得出大致调用流程


    15995833504303.jpg

    根据上边调用路程分析得出有3个方法比较重要
    1.cls->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;
    }
    

    流程如下


    15995840879417.jpg

    fastInstanceSize 方法实现
    会跳之align16计算内存大小

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

    alignedInstanceSize
    会跳转至word_align

    // Class's ivar size rounded up to a pointer-size boundary.
        uint32_t alignedInstanceSize() const {
            return word_align(unalignedInstanceSize());
        }
    

    align16
    16字节内存对齐方法

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

    下边以align16(7)为例

    带入公式
    22 & ~ 15
    
    0000 0000 0001 0110 //22
    
    0000 0000 0000 1111 //15 
    1111 1111 1111 0000 //取反
    
    0000 0000 0001 0110 & 1111 1111 1111 0000
    
    0000 0000 0001 0000 //16 最后得到结果16
    
    • 首先将 7 + 15 = 22
    • 再将15取反 得到 1111 1111 1111 0000
    • 最后将两个结果进行&操作 规则是 1^1=1 其他都是=0得到最后结果 为 16

    内存对齐的原因有

    • 1.通常内存是由一个个字节组成的,cpu在存取数据时,并不是以字节为单位存储,而是以块为单位存取,块的大小为内存存取力度。频繁存取字节未对齐的数据,会极大降低cpu的性能,所以可以通过减少存取次数来降低cpu的开销
    • 2.calloc申请内存返回指针地址
      这里我们可以通过断点来印证这一点
    2251862-b9a30d37e76a428a.png

    这里我们可以看到断点之前是 nil
    断点之后返回一个16进制地址

    • 3.obj->initInstanceIsa 类与isa 进行关联
      内存也有了 接下来就是该将内存与isa进行关联
      这里也可以通过断点来进行验证
      obj->initInstanceIsa函数调用之后我们可以得到一个结果如下图
    2251862-1a4c4c6e7f02ad57.png

    总结

    • 通过对alloc源码的分析得出 alloc主要的作用就是开辟内存 通过16字节对齐
    • 主要步骤 计算->开辟->关联

    init原理

    接下来按照上边alloc的调试方法断点跟进去得到如下两个方法

    // Replaced by CF (throws an NSException)
    + (id)init {
        return (id)self;
    }
    
    - (id)init {
        return _objc_rootInit(self);
    }
    

    跳转至_objc_rootInit

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

    可以得到结果是返回自己 也就self (id)
    返回id类型方便强转

    new原理

    通过源码可以得到

    + (id)new {
        return [callAlloc(self, false/*checkNil*/) init];
    }
    

    看到这里我们应该就明白 new = alloc + init
    在平常开发的过程当中也有用到new
    验证


    15996068675337.jpg png

    总结

    • new最后会调用到init 如果子类重写了init会先调用子类的init
    • 如果子类没重写会掉用父类的init
    • 如果使用我们alloc+自定义实现方法可以实现更多可能,相比new来说 提供了拓展性 更灵活

    相关文章

      网友评论

          本文标题:iOS 底层原理 alloc&init&new

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