001-alloc&init

前言

从一个对象的alloc开始,让我们入OC底层实现,去探索学习OC源码。

    LWPerson * obj = [LWPerson alloc];
    LWPerson * obj1 = [obj init];
    LWPerson * obj2 = [obj init];
    
    LWPerson * newObj = [LWPerson alloc];
 
    NSLog(@"%@---%p--%p",obj,obj,&obj);
    NSLog(@"%@---%p--%p",obj1,obj1,&obj1);
    NSLog(@"%@---%p--%p",obj2,obj2,&obj2);
    NSLog(@"%@---%p--%p",newObj,newObj,&newObj);
    ---0x281404710--0x16b5cdbe0
    ---0x281404710--0x16b5cdbd8
    ---0x281404740--0x16b5cdbd0
截屏2021-07-05 下午10.02.42.png

初步总结

  • alloc具有开辟一块内存的功能,而init 没有开辟内存的功能
  • ps:栈区 开辟的内存是高地址到低地址,堆区则是低地址到高地址(这里会延伸出一个奇怪的问题)

三种探索底层的方法

  1. 符号断点
  2. 汇编 Xcode -> Debug -> Debug Workflow -> Always Show Disassembly
  3. 断点 step into

源码

alloc
+ (id)alloc {
    return _objc_rootAlloc(self);
}
_objc_rootAlloc
id _objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
callAlloc
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__ //判断是不是 objc2.0版本
    //slowpath(x):x很可能为假,为真的概率很小 
    //fastpath(x):x很可能为真 
    //其实将fastpath和slowpath去掉是完全不影响任何功能,写上是告诉编译器对代码进行优化
    if (slowpath(checkNil && !cls)) return nil;
    //判断该类是否实现自自定义的 +allocWithZone,没有则进入if条件句
    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));
}
_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);
}

zone参数被丢弃,开发者即使自定义allocWithZone方法也无法指定想使用的zone,对象所在zone实际上由libmalloc库分配。

对象创建核心函数_class_createInstanceFromZone
截屏2021-07-05 下午10.19.56.png
  • cls->instanceSize: 对象所需内存大小
  • (id)calloc(1,size): 开辟内存,返回地址指针
  • obj->initInstanceIsa: 初始化isa指针,和类关联起来
  • 返回obj(如果有cxxCtor,会调用初始化)

debug

        NSObject *n =  [NSObject alloc];
        LGPerson *p0 = [LGPerson alloc];
        LGPerson *p1  = [LGPerson alloc];
        NSLog(@"测试一下");

在creatInstance函数处打了断点,分别打印三次alloc的堆栈

NSObject的alloc
截屏2021-07-05 下午10.44.34.png

流程:objc_alloc->callAloc->_objc_rootAllocWithZone->createInstance

LGPerson第一次alloc
截屏2021-07-05 下午10.47.45.png

流程:objc_alloc-> callAloc-> [NSObject alloc]-> _objc_rootAlloc->
callAlloc-> _objc_rootAllocWithZone->createInstance

LGPerson第二次alloc
截屏2021-07-05 下午10.48.41.png

流程:objc_alloc-> callAlloc-> _objc_rootAllocWithZone-> createInstance

几个疑问

从函数栈看,[ SomeClass alloc]方法调用,都被转化为objc_alloc函数调用,发生了什么,谁做的?
image.png

通过查看LLVM源码,我们发现是编译器把alloc消息发送,转化了objc_init函数调用。至于为什么?
我的理解是这里可以看做是编译器把alloc方法给hook了,在创建自定义类模板的第一个实例对象的时候,objc_alloc会再次发送alloc消息,调用alloc方法。其他时候objc_alloc函数调用会直接走到createInstane函数。对比第一次alloc,这显然减少后续对象开辟内存空间时的函数调用。

// Calls [cls alloc].
id
objc_alloc(Class cls)
{
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
为什么自定义类的第一次alloc和第二次alloc的函数调用堆栈不同呢?
  • debug调试看到hasCustomAWZ()返回值不同。我们第一次alloc多走了一段发送alloc消息的流程,在这个过程中我们的自定义类被实现,被初始化,setInitialized()函数中,cache的_flag成员关于是有CustomAWZ的标记位被重新赋值。
  • 除第一次外,后面alloc流程,自定义类如果没有CustomAWZ,就会直接走_objc_rootAllocWithZone,不会再经过消息发送,如果有就会发送allocWithZone:消息。
  • 这也侧面说明,为什么llvm会把alloc消息发送转换成objc_alloc,它既保证了我们类在第一次接收消息时被初始化,也避免了频繁的alloc消息发送,直接进去了对象空间开辟流程。
  • 看到这里是不是很期待我们消息发送流程,以及它是如何触发自定义类的初始化。
为什么NSObject的第一次alloc跟自定义类的不同,没有走alloc方法调用呢?

这里不多说了,上图


截屏2021-07-06 下午4.02.52.png
  • 很显然NSObject作为基类,在main函数之前,程序加载过程中,libobjc就被触发了NSObject的initialize方法。这也是我们在main函数执行[NSObject alloc]直接进入_objc_rootAllocWithZone的原因。
  • 这里需要注意的一点是,NSObjcet类是有自定义awz的,看下面注释,特殊处理了呀。
  // Special cases:
    // - NSObject AWZ  class methods are default.
  • 看到这是不是对我们iOS程序的加载流程也很好奇了。

画个alloc流程图吧
截屏2021-07-06 下午6.08.40.png

总结:

  • LLVM在编译时把所有的alloc消息发送,转化为objc_alloc()
  • 自定义类的alloc流程,如果类没有initialize,或者实现了customAWZ(都是customAWZ返回为true),会触发alloc消息发送,否则将会直接进_objc_rootAlloc
  • 建议不要customAWZ,因为zone参数在底层实现是被丢弃的,实际分配zone不会被开发者决定(libMaolloc分配),反而让alloc调用流程变复杂。
  • 下篇拓展下Align16,对齐

init

new

你可能感兴趣的:(001-alloc&init)