iOS 底层原理 iOS 进阶之路

OC底层原理三:探索alloc (你好,alloc大佬 )

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

OC底层原理 学习大纲

OC对象的始源 - alloc

前言

我们都知道,创建OC对象的2种方式: [[ClassName alloc]init][ClassName new]

当被问起他们的作用时,可能你的回答是: alloc + init给对象开辟内存空间并完成对象初始化,new是类方法,实现的功能一样。

这个描述没有错,但请详细描述下他们的作用。

可能你一脸懵逼。 心里已经开骂: 你丫有病吧,我已经说完了呀!!

今天,我将帮你扯开alloc、init、new的这块遮羞布。 让你深入了解alloc、init、new。

前期准备

课前问题

打开objc4-781包,在HTTest文件夹中,创建HTPerson测试文件(继承自NSObject),切换项目target为HTTest,在main.m文件中加入测试代码。

image.png

%@ 打印对象 %p 打印地址 &p 指针地址

问题:

  1. p1p2p3对象和地址打印都一致, 为何&p打印不一致
  2. p4的地址为什么和p1p2p3都不一样。

学完本章,你就彻底懂了

本节内容:

  1. alloc流程
  2. alloc核心函数
  3. alloc的地位(init、new)

1. alloc流程

打开源码工程,跟随alloc函数,一步步深入。流程如下:

alloc流程.png

当出现分支时,我们可以添加断点,辅助查看主流程是进入哪个分支。

不知道打断点,可参考OC底层原理一:定位源码(欢迎来到底层世界)内的三种断点技巧。

callAlloc处出现了分支。断点后发现程序走向_objc_rootAllocWithZone分支,继而进入_class_createInstanceFromZone函数。

image.png

关于fastpathslowpath的作用,请移步OC底层原理四: 编译器优化

allocwithZone: 和alloc一样,为对象分配足够的内存, cocoa 会遍历该对象所有的成员变量,通过成员变量的类型来计算所需占用的内存。从iOS8以后,Zone外层API已被废弃,仅底层源码做兼容处理。

_class_createInstanceFromZone是最底层的包工头。😂 终于找到真正干活的人了。它实现三大核心方法,然后将成品obj返回给外层。

_class_createInstanceFromZone核心方法.png

2.alloc核心函数

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

    if (!zone && fast) {
        // 初始化isa并与objc关联
        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);
}
1. 计算内存大小: 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;
    }

if (size < 16) size = 16 :做了小于16字节的判断。
跟断点,发现主流程进入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

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

align16的实现,就是使用位运算算法完成16字节对齐

算法(x + size_t(15)) & ~size_t(15)
x=8为例,计算过程如下:

  • 8 + size(15) = 23 二进制-> 0000 0000 0001 0111
  • size_t(15) 二进制-> 0000 0000 0000 1111
  • 取反~size_t(15) 二进制-> 1111 1111 1111 0000
  • 求交 & :
    0000 0000 0001 0111 & 1111 1111 1111 0000 = 0000 0000 0001 0000
  • 结果表示为十进制: 16

目的:

执行完后,回到上层函数size = cls->instanceSize(extraBytes)可打印size值。
此时已完成内存大小计算

image.png
2. 分配内存 calloc

根据size 大小进行内存分配

image.png
3. initInstanceIsa

初始化isa,完成与类的绑定

obj->initInstanceIsa(cls, hasCxxDtor);
inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

具体的isa结构和绑定关系,后续会作为单独章节进行讲解

isainit之后加断点,打印obj,此时发现地址与类完成绑定

image.png

总结: 至此,我们已对alloc有了完整的认知

3. alloc的地位(init、new)

可能你有疑问,alloc把活都干完了,init和new干啥?

init

进入init

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

new

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

实际上就是完成调用了callAlloc,走的时alloc流程。

唯一区别:

答案

image.png

alloc是真正开辟内存和绑定对象的,p1、p2、p3共用1个alloc,所以他们都是指向同一目的地址。但是他们本身也是对象,在init时传入他们自身id&p打印的是他们自身的地址。

通俗的说:
我有一个房子出售,A、B、C三个都是我员工,他们都领着客户来看我这套房子。但是他们三个虽然都是我公司员工,但工号(id)不一样。
如果客户问他们房子在哪(等同于%@%p打印),他们都会告诉我房子的具体位置(三人说的一定相同)。
如果顾客问他们是谁(等同于打印&p),他们就会各自回答A、B、C。

因为p1、p2、p3是同一个alloc打印的,而p4是new出来的,new会单独调用alloc。 所以他们打印肯定不一样

下一节: OC底层原理五: NSObject的alloc分析

上一篇下一篇

猜你喜欢

热点阅读