美文网首页
OC底层原理02-alloc 探索

OC底层原理02-alloc 探索

作者: AndyGF | 来源:发表于2020-09-12 19:23 被阅读0次

    本文将围绕 alloc 作用 和 作用过程进行探索.

    一. alloc 作用

    我们创建一个继承自 Person 类, 只是简单创建一个类而已, 按照下面代码创建三个对象并打印出相关内容.

    1.创建对象
    平时我们创建对象都是这样的 Person *p1 = [[Person alloc] init];, 今天我们换个套路, 分开写, 看看 alloc 方法到底做了些什么?

        Person *p1 = [Person alloc];
        Person *p2 = [p1 init];
        Person *p3 = [p1 init];
    

    2. 对 p1 p2 p3 进行如下打印

        NSLog(@"%@ -- %p -- %p",p1,p1,&p1);
        NSLog(@"%@ -- %p -- %p",p2,p2,&p2);
        NSLog(@"%@ -- %p -- %p",p3,p3,&p3);
    
    • 打印结果


      p1 p2 p3 全部内容

    分析打印结果:
    p1 p2 p3%@%p 打印结果相同, 说明p1 p2 p3指向同一块内存空间, 这块内存空间的地址是 0x282d34ba0; &p1 &p2 &p3 的值是不同的, 说明 p1 p2 p3 是三个不同的变量.

    p1 p2 p3 内存示意图

    代码 Person *p1 = [Person alloc]; : allocp1开辟了内存空间
    代码 Person *p2 = [p1 init]; : init 将 p1 指向的对象的内存空间返回给 p2
    代码 Person *p3 = [p1 init]; : init 将 p1 指向的对象的内存空间返回给 p3

    • 由此可知: init 并没有修改 alloc 申请的内存空间.

    alloc 作用总结 :

    alloc 的作用是开辟内存空间.

    福利:
    p1 p2 p3 是指针变量, 存储在内存的栈区, 每个地址是 8 个字节, 通过 p1 p2 p3 的地址可以发现, 0x16d9792580x16d979250 差值是 8 字节, 0x16d9792500x16d979248 差值是 8 字节, 说明这三个变量的地址是从高到低连续的. 如果这里一块, 那里一块, 必然有一部分空间会被浪费掉, 对于计算机而言, 内存空间是非常富贵的资源.
    栈内存是连续的, 从而避免了内存空间的浪费.

    二. alloc 开辟空间的流程

    一般情况我们想探索一个方法的执行流程, 都会点进去看方法的实现, 那么我们跳进去看看源码是怎么实现的.


    跳转操作
    alloc 声明

    当我们来到 alloc 的声明时, 想要继续跟进方法实现, 发现根本不行, 原因很简单, 这是个系统方法, 苹果是不开源的, 并且连注释都没有.

    那么我们如何进行探索呢 ?
    关于源码探索方式可以看我这篇文章 Object-C底层原理01-源码探索跟踪的三种技巧

    我们通过符号断点可以找到 alloc 方法所在的源码库为 libobjc.A.dylib, 此处为什么是 NSObject 的 alloc方法呢 ? 因为 Person 类什么方法也没实现, alloc 方法是继承自 NSObject.

    查看 alloc 源码库

    我们知道 alloc 所在的库的名称之后应该怎么办呢 ? 我们可以去苹果开源库网站找到并下载libobjc.A.dylib 这个库. 然后进行调试就可以了,

    查找源码的地址:

    地址一: 苹果开源源码地址
    地址二: 这个源码地址更直接

    本次探索我们使用 objc4-781 的版本. 打开第二个地址, 搜索 objc4 进去下载 781 版本.

    开始探索源码 :

    1. 进入 alloc 源码
    + (id)alloc {
        return _objc_rootAlloc(self);
    }
    
    1. 进入 _objc_rootAlloc 源码
    // 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*/);
    }
    
    1. 进入 callAlloc 源码
    // 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));
    }
    

    如上所示,在 calloc 源码中,已经有了分支情况处理, 当我们无法确定走哪种情况时,可以通过断点调试,判断下一步执行哪部分逻辑。通过调试发现代码会执行 _objc_rootAllocWithZone 这个方法.

    注意 :
    slowpath & fastpath
    其中关于slowpath和fastpath这里需要简要说明下,这两个都是objc源码中定义的宏,其定义如下

    //x很可能为真, fastpath 可以简称为 真值判断
    #define fastpath(x) (__builtin_expect(bool(x), 1))
    //x很可能为假,slowpath 可以简称为 假值判断
    #define slowpath(x) (__builtin_expect(bool(x), 0))
    

    其中的 __builtin_expect 指令是由 gcc 引入的,
    1、目的:编译器可以对代码进行优化,以减少指令跳转带来的性能下降。即性能优化
    2、作用:允许程序员将最有可能执行的分支告诉编译器。
    3、指令的写法为:__builtin_expect(EXP, N)。表示 EXP==N 的概率很大。
    4、fastpath定义中 __builtin_expect((x),1) 表示 x 的值为真的可能性更大;即 执行 if 里面语句的机会更大
    5、slowpath定义中的 __builtin_expect((x),0) 表示 x 的值为假的可能性更大。即执行 else 里面语句的机会更大
    6、在日常的开发中,也可以通过设置来优化编译器,达到性能优化的目的,设置的路径为:Build Setting --> Optimization Level --> Debug -->None 改为 fastest 或者 smallest

    cls->ISA()->hasCustomAWZ()
    其中 fastpath 中的 cls->ISA()->hasCustomAWZ() 表示判断一个类是否有自定义的 +allocWithZone 实现,这里通过断点调试,是没有自定义的实现,所以会执行到 _objc_rootAllocWithZone

    1. 进入 _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);
    }
    
    1. 进入 _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) {
                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);
    }
    

    恭喜你进入了 alloc 核心源码, 主要分为三大部分

    • cls->instanceSize:计算需要开辟的内存空间大小
    • calloc:申请内存,返回地址指针
    • obj->initInstanceIsa:将 类 与 isa 关联

    我们将源码分析结果绘制成流程图

    alloc 执行流程图 :

    alloc 执行流程图

    alloc 核心方法 :

    alloc 核心方法

    相关文章

      网友评论

          本文标题:OC底层原理02-alloc 探索

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