详细分析 alloc 流程

2020-02-29  本文已影响0人  恬甜咖啡糖_0301

0. 从main 入手-找一下感觉

在我们常见的app项目中,在main函数中打一个断点,并且再添加一个_objc_initd的符号断点,方便看调用栈状态。

调用栈状态

可以大概看出一个加载流程


image.png

1. 探索alloc

我们先就简单粗暴的来看看,新建一个TestNSObject 的类,然后 在alloc 处下一个断点,


截屏2020-02-29下午3.21.40.png

control + in (或者直接看汇编代码,或者符号断点)
最后可以看出,alloc最后其实是libObjc动态的objc_alloc

截屏2020-02-29下午5.02.37.png

2. 探索libObjc源码

通过断点分析,调用流程图如下:


NSObject-alloc流程2.png

配合核心源码分析

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

宏的说明

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

第二次 callAlloc 参数说明:
checkNil 是否需要判空直接传的是 false ,站在系统角度,前面已经在第一次调用 callAlloc 的时候进行了判空了,没必要再次进行判空的了。第三个参数allocWithZone 传的是 true ,关于这个方法,我查阅了苹果开发者文档,文档解释如下:

Do not override allocWithZone: to include any initialization code. Instead, class-specific versions of init... methods.
This method exists for historical reasons; memory zones are no longer used by Objective-C.
译:不要去重载 allocWithZone 并在其内部填充任何初始化代码,相反的,应该在 init... 里面进行类的初始化操作。
这个方法的存在是有历史原因的,内存 zone 已经不再被 Objective-C 所使用的。

按照苹果开发者文档的说法,其实 allocWithZone 本质上和 alloc 是没有区别的,只是在 Objective-C 远古时代,程序员需要使用诸如 allocWithZone 来优化对象的内存结构,而在当下,其实你写 alloc 和 allocWithZone 在底层是一样的。

方法里面两个if的判断

  1. if (fastpath(!cls->ISA()->hasCustomAWZ()))

    bool hasCustomAWZ() {
        return ! bits.hasDefaultAWZ();
    }
    
    bool hasDefaultAWZ() {
        return data()->flags & RW_HAS_DEFAULT_AWZ;
    }
    
    #define RW_HAS_DEFAULT_AWZ    (1<<16)
    
    struct class_rw_t {
        // Be warned that Symbolication knows the layout of this structure.
        uint32_t flags;
        ...省略
    }
    
    struct objc_class : objc_object {
        // Class ISA;
        Class superclass;
        cache_t cache;             // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
        class_rw_t *data() { 
            return bits.data();
        }
        ...省略
    }
    

这里实际上是判断cls 也就是object_class 这一结构体内部的class_rw_tflags 与上一个宏RW_HAS_DEFAULT_AWZ 的结果。
测试结论:在第一次进入 callAlloc 方法内部的时候, flags 值为 1 ,然后 flags 与上 1<<16 结果就是 0 ,所以第一次就会跳过 if 里面的逻辑;而第二次进入 callAlloc 方法内部的时候, flags 值是一个很大的整数,与上 1<<16 后结果并不为0 ,就进入 if 里面的逻辑了。

  1. if (fastpath(cls->canAllocFast()))

     bool canAllocFast() {
         assert(!isFuture());
         return bits.canAllocFast();
     }
     
     bool canAllocFast() {
         return false;
     }
    

可以看出一直返回false,所以就走到了核心 class_createInstance-> _class_createInstanceFromZone

核心创建实例 _class_createInstanceFromZone 分析

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();

    //获取cls的实例内存大小,extraBytes为0
    size_t size = cls->instanceSize(extraBytes);
    //outAllocatedSize 默认nil
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    //zone默认是nil,fast是true
    if (!zone  &&  fast) {
        //  根据实例size开辟内存
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        //将cls和是否有c++析构器传入给initInstanceIsa,实例话isa
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        //只有 allocWithZone 或 copyWithZone 会来到下面的逻辑
        if (zone) {
            // 根据给定的 zone 和 size 开辟内存
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            // 根据 size 开辟内存
            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.
        //初始化isa
        obj->initIsa(cls);
    }
    //如果有 C++ 初始化构造器和析构器,优化加速整个流程
    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}

结论:
alloc 开辟申请内存空间
伴随初始化isa

上一篇下一篇

猜你喜欢

热点阅读