iOS 开发每天分享优质文章专注iOS开发的小渣渣

OC底层探索03-常用的alloc,init,new到底做了什么

2020-09-11  本文已影响0人  Henry________
前言:想必大家对于[xxx alloc] init]非常熟悉了,都知道是创建一个xxx的对象,但是OC底层到底做了什么?

首先看下方代码:

    HRTest * t = [HRTest alloc];
    HRTest * tt = [t init];
    HRTest * ttt = [t init];
    HRTest * tttt = [HRTest alloc];
    NSLog(@"%@---%p---%p",t,t,&t);
    NSLog(@"%@---%p---%p",tt,tt,&tt);
    NSLog(@"%@---%p---%p",ttt,ttt,&ttt);
    NSLog(@"%@---%p---%p",tttt,tttt,&tttt);
常识:
  1. %p/t :是指向对象的指针 %p/&t :是指向对象指针的指针
  2. 栈区存放原则:从高位到低位; 堆区存放原则:从低位到高位
  3. 内存地址是连续的

对象的存储位置

用一张图来解释:


alloc

alloc

想要一探alloc是如何申请了内存空间的,就需要使用上篇中提到的objc源码了。废话不多说,打开源码,加上断点,一步步开始调试:
此处有两种可能,简述流程省略代码:

  1. 创建NSObjec
    直接进入alloc流程
  2. 创建继承自NSObject的自定义类
    先进入_objc_alloc->callAlloc->alloc,为什么会进入_objc_alloc而不是调用的alloc这就要涉及到llvm中的知识,后续有机会再来解释,可以简单理解为llvm做了一次类似于hook的操作,将alloc转为_objc_alloc
  3. 最终到达了_class_createInstanceFromZone,这个方法是正在来进行内存申请操作的地方
    alloc流程图

_class_createInstanceFromZone

_class_createInstanceFromZone流程图
init
//此处只放出最核心代码
_class_createInstanceFromZone(...){
    ...
    size_t size;
    size = cls->instanceSize(extraBytes);
}

size_t instanceSize(size_t extraBytes) const {
    //编译快速计算所占内存大小
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(extraBytes);
        }
        size_t size = alignedInstanceSize() + extraBytes;
        if (size < 16) size = 16;
        return size;
    }
    
size_t fastInstanceSize(size_t extra) const
    {...
    //计算实际内存占用
    size_t size = _flags & FAST_CACHE_ALLOC_MASK;
     return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
    }
    
static inline size_t align16(size_t x) {
    //著名的字节对齐算法
    return (x + size_t(15)) & ~size_t(15);
}
calloc
//此处只放出最核心代码
_class_createInstanceFromZone(...){
    ...
    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        // 申请1块size大小的内存空间
        obj = (id)calloc(1, size);
    }
}
initInstanceIsa
//此处只放出最核心代码
_class_createInstanceFromZone(...){
    ...
    id obj;

    if (!zone && fast) {
        //一般会进入此判断
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        obj->initIsa(cls);
    }
}

objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ...
    //进行类型赋值
    isa = isa_t((uintptr_t)cls);
}

看完全部流程是不是感觉到流程索然繁杂,但是本质并不复杂,[狗头]

核心步骤:计算内存大小 - 申请内存 - 进行类的关联

fastpath、slowpath

在源码中反复出现的这两个宏定义,我觉得有必要简单解释一下:

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

1,自定义类第一次callAlloc时没有找到默认的allocWithZone,经过objc_msgsend(alloc)之后,第二次callAlloc时找到了默认的allocWithZone。allocWithZone是什么时候创建加载的呢?

init做了什么

- (id)init {
    return _objc_rootInit(self);
}

id _objc_rootInit(id obj)
{
    return obj;
}

new做了什么

一般在开发中,初始化除了init,还会使用new,通过源码来看两者本质上并没有什么区别

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

但是在一般的开发中,如果使用自定的类,这里并不建议使用new,因为这里系统只会调用init方法,对于自定义的initWhitXXX并不会调用。但是系统自己类大可放心使用.

参考资料:
fastpath slowpath
iOS 内存字节对齐

上一篇下一篇

猜你喜欢

热点阅读