OC底层原理02-alloc 探索

2020-09-12  本文已影响0人  AndyGF

本文将围绕 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%@%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

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 核心源码, 主要分为三大部分

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

alloc 执行流程图 :

alloc 执行流程图

alloc 核心方法 :

alloc 核心方法
上一篇下一篇

猜你喜欢

热点阅读