iOS探索 alloc流程

10,346 阅读8分钟

欢迎阅读iOS探索系列(按序阅读食用效果更加)

写在前面

OC作为一门万物皆对象的语言,那么对于对象创建、开辟内存的了解必不可少,本文就将探索一下alloc在底层的具体步骤

官方源码

Cooci司机objc4-756.2调试方案(Xcode11暂时无法断点进源码)

一、探索方向

在源码中,我们可以通过Command+单击/右击->Jump to Defintion的方式进入alloc调用方法,脱离了源码我们又该如何知道它调用了什么底层方法呢?

在对象创建的代码处下个断点,等执行到断点处使用以下方法:

  • Control+Step into
  • 符号断点
  • 菜单栏Debug->Debug Workflow->Always Show Disassembly(始终显示汇编代码)

这三种方法都能得出调用了objc_alloc方法

二、开始探索

//
//  main.m
//  FXTest
//
//  Created by mac on 2019/12/19.
//

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "FXPerson.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSObject *object1 = [NSObject alloc];
        FXPerson *object2 = [FXPerson alloc];
    }
    return 0;
}

不出意料,各位都能来到如下方法

+ (id)alloc {
    return _objc_rootAlloc(self);
}
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

但是接下来的源码就会让你头晕目眩,不想看了

// Call [cls alloc] or [cls allocWithZone:nil], with appropriate 
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    if (slowpath(checkNil && !cls)) return nil;

#if __OBJC2__
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        // No alloc/allocWithZone implementation. Go straight to the allocator.
        // fixme store hasCustomAWZ in the non-meta class and 
        // add it to canAllocFast's summary
        if (fastpath(cls->canAllocFast())) {
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor();
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            // Has ctor or raw isa or something. Use the slower path.
            id obj = class_createInstance(cls, 0);
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            return obj;
        }
    }
#endif

    // No shortcuts available.
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}

本来看源码就枯燥,还有这么多if-else逻辑岔路口,就会有很多人关闭了Xcode

看啥不好看源码,是嫌自己头发太旺盛吗?

别急,我这里已经帮你掉过头发了(捋过思路了)

三、alloc源码流程

1.坑——objc_alloc、alloc傻傻分不清楚

这个坑无伤大雅,了解即可;可以简单理解为OC对象alloc->alloc

不知道你有没有发现奇怪的一点,第二节探索方向中明明调用的是底层objc_alloc方法,为什么在创建对象处跟进源码会来到alloc方法呢?

函数调用栈也略有问题

这个问题还与Xcode版本有关,Xcode11->objc_alloc,Xcode10->alloc,实在令人百思不得其解

对于这个问题,目前的说法是源码开源得不够充分。以下这段代码虽然未调用到,但其逻辑也是耐人寻味。 大致猜测是这个方法等于交换单次的Method Swizzling(既然官方不开源,说明无关痛痒)

第一处:注释告诉你会调起[cls alloc]

// Calls [cls alloc].
id
objc_alloc(Class cls)
{
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}

另一处:如果出问题就fixMessageRef(下断点不会被调用)

2.alloc、_objc_rootAlloc方法

前面提及过的两个方法

+ (id)alloc {
    return _objc_rootAlloc(self);
}
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

3.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 (slowpath(checkNil && !cls)) return nil;

#if __OBJC2__
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        // No alloc/allocWithZone implementation. Go straight to the allocator.
        // fixme store hasCustomAWZ in the non-meta class and 
        // add it to canAllocFast's summary
        if (fastpath(cls->canAllocFast())) {
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor();
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            // Has ctor or raw isa or something. Use the slower path.
            id obj = class_createInstance(cls, 0);
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            return obj;
        }
    }
#endif

    // No shortcuts available.
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}

①if (slowpath(checkNil && !cls))判断

fastpath(x)表示x很可能不为0,希望编译器进行优化;slowpath(x)表示x很可能为0,希望编译器进行优化——这里表示cls大概率是有值的,编译器可以不用每次都读取 return nil 指令

②if (fastpath(!cls->ISA()->hasCustomAWZ()))判断

hasCustomAWZ实际意义是hasCustomAllocWithZone——这里表示有没有alloc / allocWithZone的实现(只有不是继承NSObject/NSProxy的类才为true)

③if (fastpath(cls->canAllocFast()))判断

内部调用了bits.canAllocFast默认为false

④id obj = class_createInstance(cls, 0)

内部调用了_class_createInstanceFromZone(cls, extraBytes, nil)

这里有个id obj,尝试着控制台打印一下

我们已经找到了我们想要的结果,接下来我们探索下_class_createInstanceFromZone方法是怎么将obj创建出来的

4._class_createInstanceFromZone方法

/***********************************************************************
* class_createInstance
* fixme
* Locking: none
**********************************************************************/

static __attribute__((always_inline)) 
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if (!cls) return nil;

    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();

    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}

①hasCxxCtor()

hasCxxCtor()是判断当前class或者superclass是否有.cxx_construct 构造方法的实现

②hasCxxDtor()

hasCxxDtor()是判断判断当前class或者superclass是否有.cxx_destruct 析构方法的实现

③canAllocNonpointer()

anAllocNonpointer()是具体标记某个类是否支持优化的isa

④instanceSize()

instanceSize()获取类的大小(传入额外字节的大小)

已知zone=false,fast=true,则(!zone && fast)=true

⑤calloc()

用来动态开辟内存,没有具体实现代码,接下来的文章会讲到malloc源码

⑥initInstanceIsa()

内部调用initIsa(cls, true, hasCxxDtor)初始化isa

这一步已经完成了初始化isa并开辟内存空间,那我们来看看instanceSize做了什么

5.字节对齐

#ifdef __LP64__
#   define WORD_SHIFT 3UL
#   define WORD_MASK 7UL
#   define WORD_BITS 64
#else
#   define WORD_SHIFT 2UL
#   define WORD_MASK 3UL
#   define WORD_BITS 32
#endif

static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() {
    assert(isRealized());
    return data()->ro->instanceSize;
}

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

size_t instanceSize(size_t extraBytes) {
    size_t size = alignedInstanceSize() + extraBytes;
    // CF requires all objects be at least 16 bytes.
    if (size < 16) size = 16;
    return size;
}

下面按调用顺序讲解

①size_t instanceSize(size_t extraBytes)

前面讲过——获取类的大小

②alignedInstanceSize()

获取类所需要的内存大小

③unalignedInstanceSize()

data()->ro->instanceSize就是获取这个类所有属性内存的大小。这里只有继承NSObject的一个属性isa——返回8字节

④word_align

顾名思义,字节对齐——64位系统下,对象大小采用8字节对齐

⑤if (size < 16) size = 16

CoreFoundation需要所有对象之和至少是16字节

6.字节对齐算法

假如: x = 9,已知WORD_MASK = 7

 x + WORD_MASK = 9 + 7 = 16
 WORD_MASK 二进制 :0000 0111 = 7 (4+2+1)
 ~WORD_MASK : 1111 1000
 16二进制为  : 0001 0000
  
 1111 1000
 0001 0000
---------------
 0001 0000 = 16

 所以 x = 16    也就是 8的倍数对齐,即 8 字节对齐

总结:对象大小为16字节,必定是8的倍数

这里有个疑问:为什么要使用8字节对齐算法呢?

简单画了个示意图,上边是紧紧挨着,下面是8字节为一格。如果cpu存数据的时候紧紧挨着,读取的时候要不断变化读取长度,所以这时候就采用了空间换时间的做法

那为什么是8字节?不是4字节或是16字节?

——因为内存中8字节的指针比较多

7.alloc实际流程图

instanceSize计算内存大小——量房子

calloc申请开辟内存——造房子

initInstanceIsa指针关联对象——房子写下名字

四、init & new

init什么也不做,就是给开发者使用工厂设计模式提供一个接口

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

- (id)init {
    return _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;
}

补充:关于子类中if (self = [super init])为什么要这么写——子类先继承父类的属性,再判断是否为空,如若为空没必要进行一系列操作了直接返回nil

new等于先调用alloc,再init

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

写在后面

研究源码必然是枯燥的,但是面对源码不用害怕,一步步把它拆分开来研究,多利用官方给的注释/Github大神的注释,慢慢也就啃下来了。

看别人学习津津有味,不如自己上手实际玩一下会更有收获;只看不练永远学不会,或许你学的正是别人错误的理论呢