alloc&init探索

作者: ssRing | 来源:发表于2020-09-05 10:38 被阅读0次

    main函数的加载流程

    1.在int main前面打个断点

    #import <UIKit/UIKit.h>
    #import "AppDelegate.h"
    
    int main(int argc, char * argv[]) {    //在这一行前面打个断点
        NSString * appDelegateClassName;
        @autoreleasepool {
            // Setup code that might create autoreleased objects goes here.
            appDelegateClassName = NSStringFromClass([AppDelegate class]);
        }
        return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
    

    然后增加一个_objc_init的符号断点




    记得关闭左下角show only stack frames with debug symbols and between libraries的按钮
    可以看到Xcode左侧的调用堆栈视图如下图:



    通过上面的调用堆栈信息,可以得出一个简略的加载流程:

    alloc&init初步探索

    在ViewController.m文件中调用alloc和init方法:

    NSObject *p1 = [NSObject alloc];
    NSObject *p2 = [p1 init];
    NSObject *p3 = [p1 init];
    NSLog(@"%@ - %p", p1, &p1);
    NSLog(@"%@ - %p", p2, &p2);
    NSLog(@"%@ - %p", p3, &p3);
    

    可以发现打印出来是这样:



    打印出来的是同一个对象,p1,p2,p3的指针地址是不同的,但指向同一块内存,说明alloc已经创建了对象,那么对象是怎么创建的呢?
    这时我们需要查看alloc的源码实现。
    按住command和control键,点击alloc,进入头文件NSObject.h,只能看到alloc方法的声明,未能找到alloc方法的实现,看起来无从下手。
    可以通过添加符号断点来解决这个问题,选择 Symbolic Breakpoint,在Symbol那一栏添加objc_alloc



    运行程序,可以看到我们需要查找的动态链接库为libobjc.A.dylib:

    探索libobjc源码

    下载libobjc源码,进行配置,然后新建一个命令行项目,运行如下代码:

    NSObject *obj = [NSObject alloc];
    

    下符号断点objc_alloc,首先进入的是objc_alloc:

    // Calls [cls alloc].
    id
    objc_alloc(Class cls)
    {
        return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
    }
    

    然后会来到callAlloc方法,注意此时第三个参数的值为false:

    static ALWAYS_INLINE id
    callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
    {
        // 判断传入的 checkNil 是否进行判空操作
        if (slowpath(checkNil && !cls)) return nil;
    
        // 如果当前编译环境为 OC 2.0
    #if __OBJC2__
        // 当前类没有自定义的 allocWithZone
        if (fastpath(!cls->ISA()->hasCustomAWZ())) {
            // No alloc/allocWithZone implementation. Go straight to the allocator.
            // 既没有实现 alloc,也没有实现 allocWithZone 就会来到这里,下面直接进行内存开辟操作。
            // fixme store hasCustomAWZ in the non-meta class and 
            // add it to canAllocFast's summary
            // 修复没有元类的类,用人话说就是没有继承于 NSObject
            // 判断当前类是否可以快速开辟内存,注意,这里永远不会被调用,因为 canAllocFast 内部
            // 返回的是false
            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];
    }
    

    由于allocWithZone为false且cls为NSObject,所以会来到最后一句return [cls alloc];,接下来是alloc方法:

    + (id)alloc {
        return _objc_rootAlloc(self);
    }
    
    // Base class implementation of +alloc. cls is not nil.
    // Calls [cls allocWithZone:nil].
    id
    _objc_rootAlloc(Class cls)
    {
        return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
    }
    

    这里第二次调用callAlloc方法,但参数不一样,第二个参数 checkNil 是 false ,因为前面在第一次调用 callAlloc 的时候进行了判空,所以这里没必要再次进行判空了。第三个参数 allocWithZone 传的是 true ,其实 allocWithZone 本质上和 alloc 是没有区别的,只是在 Objective-C 远古时代,程序员需要使用诸如 allocWithZone 来优化对象的内存结构,而在当下,其实 alloc 和 allocWithZone 在底层是一样的。
    首先来到if (fastpath(!cls->ISA()->hasCustomAWZ())),判断当前类是否没有自定义的 allocWithZone:

     bool hasCustomAWZ() {
        return ! bits.hasDefaultAWZ();
     }   
    
     bool hasDefaultAWZ() {
         return data()->flags & RW_HAS_DEFAULT_AWZ;
     }
    

    这里的flag指的是cls结构体内部的class_rw_t的flags,而RW_HAS_DEFAULT_AWZ 则是用来标示当前的class或者是superclass是否有默认的alloc/allocWithZone:的,这个值存储在metaclass 中。
    在第一次进入callAlloc方法时,flags的值为1,与上1<<16结果为0,返回的是 false,在hasCustomAWZ这里取反,变为true,然后再在if (fastpath(!cls->ISA()->hasCustomAWZ()))里取反,为false,于是不执行if当中的内容。
    第二次进入callAlloc方法时,flags的值是一个很大的整数,与上1<<16结果为1,与上面恰好相反,于是执行if当中的内容。
    首先是if (fastpath(cls->canAllocFast()))

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

    可以看出,canAllocFast总是返回false,于是接下来是:

    id obj = class_createInstance(cls, 0);
    if (slowpath(!obj)) return callBadAllocHandler(cls);
    return obj;            
    

    接下来是class_createInstance方法:

    id 
    class_createInstance(Class cls, size_t extraBytes)
    {
        return _class_createInstanceFromZone(cls, extraBytes, nil);
    }
    
    static __attribute__((always_inline)) 
    id
    _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                                  bool cxxConstruct = true, 
                                  size_t *outAllocatedSize = nil)
    {
        // 对 cls 进行判空操作
        if (!cls) return nil;
        // 断言 cls 是否实现了
        assert(cls->isRealized());
    
        // Read class's info bits all at once for performance
        // cls 是否有 C++ 的初始化构造器
        bool hasCxxCtor = cls->hasCxxCtor();
        // cls 是否有 C++ 的析构器
        bool hasCxxDtor = cls->hasCxxDtor();
        // cls 是否可以分配 Nonpointer,如果是,即代表开启了内存优化 
        bool fast = cls->canAllocNonpointer();
    
        // 这里传入的 extraBytes 为0,然后获取 cls 的实例内存大小
        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);
            // 如果开辟失败,返回 nil
            if (!obj) return nil;
            // 将 cls 和是否有 C++ 析构器传入给 initInstanceIsa,实例化 isa
            obj->initInstanceIsa(cls, hasCxxDtor);
        } 
        else {
            // 如果 zone 不为空,经过笔者测试,一般来说调用 alloc 不会来到这里,只有 allocWithZone
            // 或 copyWithZone 会来到下面的逻辑
            if (zone) {
                // 根据给定的 zone 和 size 开辟内存
                obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
            } else {
                // 根据 size 开辟内存
                obj = (id)calloc(1, size);
            }
            // 如果开辟失败,返回 nil
            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的全部流程了,下面是简略的流程图:


    init简略分析

    init 内部实现十分简单,先来到的是 _objc_rootInit ,然后就直接返回 obj 了。其实这里是一种抽象工厂设计模式的体现,对于 NSObject 自带的 init 方法来说,其实啥也没干,但是如果你继承于 NSObject 的话,就可以去重写 initWithXXX 之类的初始化方法来做一些初始化操作。

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

    相关文章

      网友评论

        本文标题:alloc&init探索

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