美文网首页
一 、alloc&init&new流程

一 、alloc&init&new流程

作者: Mlqq | 来源:发表于2020-09-25 15:25 被阅读0次

    要看流程肯定要看源码,可以下载objc4最新的781版本,参考这里自己配置成可编译状态,也可以直接下载已经编译好的。

    下载完成之后,创建自己的target


    image.png

    然后选择


    image.png
    创建完成之后,要修改下面的配置否则,断点不能跟进源码
    image.png image.png

    1. alloc 流程

    进过上面的准备工作,现在就可以轻松探索alloc流程了。

    1.1 alloc做了什么事情

    创建一个MlqqObject继承自NSObject,然后调用alloc方法,然后打印一下

    image.png 从打印信息可以看到,有MlqqObject这个类名,还有一个地址0x10069ebf0,有地址说明向系统申请了空间,有空间就有大小,这是从打印信息里面可以看出来的,究竟是不是,我们往下看。

    1.2 断点跟踪

    image.png

    跟断点进来

    image.png 可见alloc方法里面直接调用_objc_rootAlloc这个方法,跟进去_objc_rootAlloc image.png _objc_rootAlloc这个方法里面调用了callAlloc,继续跟进去callAlloc image.png callAlloc里面调用_objc_rootAllocWithZone继续跟进_objc_rootAllocWithZone

    这个方法里面还有一行代码return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));这个方法也会执行,因为他是在后面并不是在#if 与 #endif 之间,但是这里为什么断在_objc_rootAllocWithZone这里呢,这个问题在后面解释,先把流程走完。

    image.png _objc_rootAllocWithZone里面调用_class_createInstanceFromZone方法,继续跟_class_createInstanceFromZone image.png 终于在_class_createInstanceFromZone方法里面看到return了,
    在此之前的所有调用方面里面都是直接return调用方法的返回值,所以_class_createInstanceFromZone才是我们研究的重点,下面是_class_createInstanceFromZone的代码
    /***********************************************************************
    * class_createInstance
    * fixme
    * Locking: none
    *
    * Note: this function has been carefully written so that the fastpath
    * takes no branch.
    **********************************************************************/
    static ALWAYS_INLINE id
    _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                                  int construct_flags = OBJECT_CONSTRUCT_NONE,
                                  bool cxxConstruct = true,
                                  size_t *outAllocatedSize = nil)
    {
        ASSERT(cls->isRealized());
    
        // Read class's info bits all at once for performance
        bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
        bool hasCxxDtor = cls->hasCxxDtor();
        bool fast = cls->canAllocNonpointer();
        size_t size;
        size = cls->instanceSize(extraBytes);
        if (outAllocatedSize) *outAllocatedSize = size;
    
        id obj;
        if (zone) {
            obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (slowpath(!obj)) {
            if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
                return _objc_callBadAllocHandler(cls);
            }
            return nil;
        }
    
        if (!zone && fast) {
            obj->initInstanceIsa(cls, hasCxxDtor);
        } else {
            // Use raw pointer isa on the assumption that they might be
            // doing something weird with the zone or RR.
            obj->initIsa(cls);
        }
    
        if (fastpath(!hasCxxCtor)) {
            return obj;
        }
    
        construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
        return object_cxxConstructFromClass(obj, cls, construct_flags);
    }
    

    在这个方法里面我们关注三行代码

    • size = cls->instanceSize(extraBytes);
    • obj = (id)calloc(1, size);
    • obj->initInstanceIsa(cls, hasCxxDtor);

    从这三个方法的名字可以看出三个方法的作用分别是计算类所需要的大小、开辟空间、初始化isa(也就是说将类与开辟的空间绑定)。

    关于计算类所需空间的大小开辟空间初始化isa三个部分,每个部分涉及的知识点都很多,所以分了三篇文章详细说明

    • alloc总结: image.png

    1.3 callAlloc 方法分析

    在上面我们看了 calloc里面是走到了_objc_rootAllocWithZone里面,那么再看下面:

    image.png 这一次为什么调用了objc_msgSend方法,而且并不是从_objc_rootAlloc进来的,而是从objc_alloc进来的?

    其实上面总结的alloc流程是在这之后的,也就是说calloc方法会走两遍,第一遍从objc_alloc进来,就是我们现在看到的情况,向cls发送alloc消息之后才会真正开始调用+ (id)alloc方法

    为什么走两遍,两遍却调用不同的方法呢,我们来分析一下calloc的源码:

    static ALWAYS_INLINE id
    callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
    {
    #if __OBJC2__
        if (slowpath(checkNil && !cls)) return nil;
        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));
    }
    

    (1)if (slowpath(checkNil && !cls))

    因为cls不可能为空 所以 这个if不会执行

    (2)if (fastpath(!cls->ISA()->hasCustomAWZ()))
    这个分支主要是hasCustomAWZ()这个方法的值,这个方法不知道是什么意思,AWZ就是allocWithZone的简写,这个方法就是判断有没有自定义的allocWithZone方法的。
    为什么有这个判断,看一下苹果官方的关于allocWithZone的文档:

    image.png 这个方法的存在是由于历史原因,现在已经不在用了。猜想应该是MRC时代的产物,现在苹果还是做了兼融,总要为自己的历史负责的嘛。

    看一下hasCustomAWZ()的源码:

    bool hasCustomAWZ() const {
            return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
        }
    

    翻译一下这个方法:不是默认的allocWithZone就是有自定义的allocWithZone

    第一次hasCustomAWZ()返回为false,说明还没有设置为默认的allocWithZone,那是什么时候设置的呢,就在hasCustomAWZ()的下面就是setHasDefaultAWZ()方法:

    void setHasDefaultAWZ() {
            cache.setBit(FAST_CACHE_HAS_DEFAULT_AWZ);
        }
    

    可以在这个打个断点,可以看到:

    image.png 可以从调用堆栈中看到正是在调了objc_msgSend之后,调setHasDefaultAWZ()方法,这个方法是在_finishInitializing方法之后,也就是说只要调了+ (id)initialize方法,系统就会调setHasDefaultAWZ()设置是否有自定义的allocWithZone方法,而对cls 发送alloc消息正好先触发+ (id)initialize方法,等调用alloc->_objc_rootAlloc->callAlloc这个流程(也就是第二遍)setHasDefaultAWZ()已经被调用,所以就可以判断出来有没有自定义的allocWithZone方法。
    为了验证是不是这样,在MlqqObject类加下面代码: image.png 上面代码的作用是类被加载的时候就调一下+ (id)initialize方法:
    再次运行: image.png 可以看到即使是从objc_alloc进来的也会调用_objc_rootAllocWithZone方法。所以calloc方法只会走一遍。

    那我们在MlqqObject类里面重写allocWithZone方法

    image.png 看一下运行结果: image.png 重写了allocWithZone方法,hasCustomAWZ()返回为false,而
    id objc_alloc(Class cls)
    {
        return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
    }
    

    第三个参数为false,所以 if (allocWithZone)不会走, 所以又走到了objc_msgSend方法,接下来又是alloc->_objc_rootAlloc->callAlloc这个流程,而

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

    第三个参数为true,所以 if (allocWithZone)会走

    image.png 所以这种情况也会走两遍。

    2 init方法

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

    系统默认的init方法什么也没做,只是提供了一个方法供开发者去重写。

    3 new方法

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

    new方法调了两个方法callAllocinit
    (1) callAllocalloc流程里面是一样的,只不过第三个参数为false,所以不支持自定义的allocWithZone方法。
    (2)默认调用的是init方法,如果自己重写了init方法,会调用自己重写的,如果没有重写就会调用默认的。如果自定了其他的init方法,比如说initWithString,别人如果使用new初始化的时候是不会调用的。
    所以基于以上两点,不建议使用new方法,不过如果不存在上面两种情况,使用new,也是没有问题的。

    相关文章

      网友评论

          本文标题:一 、alloc&init&new流程

          本文链接:https://www.haomeiwen.com/subject/ffefuktx.html