美文网首页
iOS对象原理探究:alloc & init & new

iOS对象原理探究:alloc & init & new

作者: 打碟的DJ | 来源:发表于2020-09-07 10:27 被阅读0次

    在开始研究之前,先看如下这端代码的打印信息:

    Person *p1 = [Person alloc];
    Person *p2 = [p1 init];
    Person *p3 = [p1 init];
    
    NSLog(@"%@",p1); // <Person: 0x6000004f4470>
    NSLog(@"%@",p2); // <Person: 0x6000004f4470>
    NSLog(@"%@",p3); // <Person: 0x6000004f4470>
    

    可以发现,对象的地址是一致的,那么我们再看下下面这段代码的打印信息:

    Person *p1 = [Person alloc];
    Person *p2 = [p1 init]; 
    Person *p3 = [p1 init]; 
    
    NSLog(@"%@ -- %p",p1, &p1); //<Person: 0x6000004f4470> -- 0x7ffee93aa248
    NSLog(@"%@ -- %p",p2, &p2); // <Person: 0x6000004f4470> -- 0x7ffee93aa240
    NSLog(@"%@ -- %p",p3, &p3); // <Person: 0x6000004f4470> -- 0x7ffee93aa238
    

    可以发现三个对象的指针地址是不一致的;
    因此我们可以知道,alloc 会去申请 Person 对象的一块内存空间,然后会用一个指针来指向这块申请的内存空间,即 p1,而 init 不会对申请的内存空间做任何的操作,而是指向申请的同一片内存空间。

    那么 alloc 是如何来申请内存空间的呢?

    alloc 如何申请内存空间

    首先我们要定位出源码来自于哪里,然后才能着重去分析源码,那么下面我们就三种定位源码出处的方式;

    定位源码出处

    一、下符号断点来直接跟流程

    • 1、先打断点到需要跟踪的创建对象的alloc下
    • 2、在打符号断点 alloc,点击下一步,发现进入 [NSObject alloc] 方法,因为 Person 没有 alloc 方法,是继承自 NSObject 方法的
    • 3、按住 control + step into 进入 _objc_rootAlloc
    • 4、发现最后的开源库来自 libobjc.A.dylib

    二、通过按住 control + step into

    • 1、先打断点到需要跟踪的创建对象的alloc下
    • 2、按住 control + step into,多点击几次
    • 3、复制 objc_alloc,打符号断点
    • 4、发现最后的开源库来自 libobjc.A.dylib

    三、汇编查看跟流程

    • 1、先打断点到需要跟踪的创建对象的alloc下
    • 2、Xcode 菜单栏中选择 Debu -> Debug Workflow -> Always Show Disassembly 进入汇编界面
    • 3、复制 objc_alloc,打符号断点
    • 4、发现最后的开源库来自 libobjc.A.dylib
    苹果开源源码汇总
    Source Browser

    alloc 流程

    • 1、下载最新的 objc 源码
    • 2、找到 alloc 方法,发现 alloc 方法中调用了 _objc_rootAlloc 方法
    • 3、_objc_rootAlloc 中调用了 callAlloc 方法
    • 4、callAlloc 中会调用 _objc_rootAllocWithZoneobjc_msgSend
    • 5、在 _objc_rootAllocWithZone 中会调用 _class_createInstanceFromZone 方法
    • 6、在 _class_createInstanceFromZone 中才是实现了主要的功能

    当我们打符号断点,在走流程的过程中,发现并没有走到 callAllco 方法中, 原因是什么呢?

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

    上面是 callAllco 方法的源码实现,其中 if 语句中用到了 slowpath 和 fastpath,这是苹果编译器优化的结果。一般我们打包 release 版本的时候,会自动勾选上编译器优化选项。

    _class_createInstanceFromZone 方法分析

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

    _class_createInstanceFromZone 方法中主要是三个方法来做事情的;zone 在 arc 下已经不再使用了,所以不做研究;

    instanceSize 方法

    size_t instanceSize(size_t extraBytes) const {
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(extraBytes);
        }
    
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }
    
    size_t fastInstanceSize(size_t extra) const
    {
        ASSERT(hasFastInstanceSize(extra));
    
        if (__builtin_constant_p(extra) && extra == 0) {
            return _flags & FAST_CACHE_ALLOC_MASK16;
        } else {
            size_t size = _flags & FAST_CACHE_ALLOC_MASK;
            // remove the FAST_CACHE_ALLOC_DELTA16 that was added
            // by setFastInstanceSize
            return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
        }
    }
    
    static inline size_t align16(size_t x) {
        // 进行字节对齐,16的倍数
        return (x + size_t(15)) & ~size_t(15);
    }
    

    init 方法

    + (id)init {
        return (id)self;
    }
    
    - (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 方法中是返回的当前对象,也就是自己本身。所以我们可以了解到 init 其实三个工厂方法,可以交给子类去自定义重写该方法。

    new

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

    通过 new 的源码可以看出,其实它也是调用了 [callAlloc init] 方法;
    但是我们推荐使用 [alloc init] 方法,因为这样我们可以自定义 init 方法,使我们的开发更加的灵活。

    相关文章

      网友评论

          本文标题:iOS对象原理探究:alloc & init & new

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