iOS探索 alloc流程

写在前面

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

官方源码算法

Cooci司机objc4-756.2调试方案(Xcode11暂时没法断点进源码)设计模式

1、探索方向

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

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

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

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

2、开始探索

//
// 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*/);
}
复制代码

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

// 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学习

看啥很差看源码,是嫌本身头发太旺盛吗?优化

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

3、alloc源码流程

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

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

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

函数调用栈也略有问题

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

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

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 字节对齐
复制代码

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

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

那为何是8字节?不是4字节或是16字节?

——由于内存中8字节的指针比较多

7.alloc实际流程图

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

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

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

4、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大神的注释,慢慢也就啃下来了。

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

相关文章
相关标签/搜索