美文网首页
对象alloc那点事(一)

对象alloc那点事(一)

作者: 会跑的鱼_09 | 来源:发表于2020-09-05 23:36 被阅读0次

在平常开发中我们会经常用到自定义类,并且会调用其alloc+init方法来创建一个实例对象。但是这个过程到底发生了什么,对象是如何创建的,如果其占用的内存大小一直没有深究过,今天就来好好掰扯掰扯~~

一、对象创建初探

定义一个LGPerson类,什么属性都不添加,然后分别打印经过alloc和init后的对象地址:

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

输出如下:
<LGPerson: 0x600002135480> - 0x600002135480 - 0x7ffee0936198
<LGPerson: 0x600002135480> - 0x600002135480 - 0x7ffee0936190
<LGPerson: 0x600002135480> - 0x600002135480 - 0x7ffee0936188
  • p1,p2,p3都指向同一个对象,说明init并不会开辟新的内存。
  • 第一个%p 打印指针信息,所以也是0x600002135480。
  • 第二个%p 打印指针的地址,即p1,p2,p3在栈上的地址,因为它们在相邻的变量所以在栈上地址是连续的,另外在64位系统上指针占8个字节,所以它们之间地址相差8。

二、分析alloc内部流程

0x600002135480这块内存是如何开辟并初始化的呢,想要知道具体的实现流程我们可以借助ios开源runtime代码来进行动态调试,具体项目搭建可以参考这里最新macOS 10.15下objc4-779.1源码编译调试。在开源项目上进行测试代码如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *objc1 = [LGPerson alloc];
        LGPerson *objc2 = [LGPerson alloc];
        NSLog(@"Hello, World! %@ - %@",objc1,objc2);
    }
    return 0;
}
objc1对象创建流程:

跟踪调试[LGPerson alloc]方法执行过程,整个堆栈调用情况如下:

截屏2020-09-05 下午1.03.29.png
最终会调用到这个_class_createInstanceFromZone,它就是我们oc类对象创建时最终调用的方法,其中有几个关键步骤,解释一下:
//步骤一:确认创建类需要开辟的内存大小
size = cls->instanceSize(extraBytes);
//步骤二:开辟内存
obj = (id)calloc(1, size);
//步骤三:把isa信息填充到上面申请到的内存中
obj->initInstanceIsa(cls, hasCxxDtor);
objc2对象创建流程:

在LGPerson *objc2 = [LGPerson alloc];打个断点,然后执行到此处后再到_class_createInstanceFromZone打个断点,整个堆栈调用情况如下:

截屏2020-09-05 下午1.03.53.png
这里发现LGPerson类第二次调用alloc方法是比第一次流程少了callAlloc[NSObject alloc]objc_rootAlloc,跟踪代码发现两次创建对象callAlloc方法中执行流程不一样:
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
    if (slowpath(checkNil && !cls)) return nil;
    //第一次LGPerson类调用alloc时,该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);
    }
    //第一次LGPerson类调用alloc时,会执行到此处通过objc_msgSend调用了alloc,后续流程又会回到当前方法中来
    return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}

第一个LGPerson对象创建时,系统底层还未加载过LGPerson类到内存中来,所以调用cls->ISA()->hasCustomAWZ()方法不成立,会进入到下面的objc_msgSend流程中。在objc_msgSend方法流程中会把LGPerson类信息读取进来,并进行缓存,以及一些类的初始化(整体流程我们后面在起一篇文章详细分析下)。所以,当创建第二个objc2对象时,类信息已经加载到内存中了,此时会直接进入到_objc_rootAllocWithZone方法中。

三、instanceSize方法分析

前面分析到对象创建有三个关键步骤,其中第一步就是确定对象需要开辟的内存大小,先看一下instanceSize方法的实现:

    // 1.调用类的instanceSize方法
    size = cls->instanceSize(extraBytes);
    
    size_t instanceSize(size_t extraBytes) const {
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            //2.直接进入此处,因为类信息被加载到内存中后会被缓存
            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;
    }
    //cache对象的
    size_t fastInstanceSize(size_t extra) const
    {
        ASSERT(hasFastInstanceSize(extra));

        if (__builtin_constant_p(extra) && extra == 0) {
            return _flags & FAST_CACHE_ALLOC_MASK16;
        } else {
            //3.通过_flags来确定对象创建需要的内存大小
            size_t size = _flags & FAST_CACHE_ALLOC_MASK;
            //4.进一步处理,并进行16字节对齐
            // 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) {
        return (x + size_t(15)) & ~size_t(15);
    }

    void setFastInstanceSize(size_t newSize)
    {
        // Set during realization or construction only. No locking needed.
        uint16_t newBits = _flags & ~FAST_CACHE_ALLOC_MASK;
        uint16_t sizeBits;

        // Adding FAST_CACHE_ALLOC_DELTA16 allows for FAST_CACHE_ALLOC_MASK16
        // to yield the proper 16byte aligned allocation size with a single mask
        sizeBits = word_align(newSize) + FAST_CACHE_ALLOC_DELTA16;
        sizeBits &= FAST_CACHE_ALLOC_MASK;
        if (newSize <= sizeBits) {
            newBits |= sizeBits;
        }
        _flags = newBits;
    }
    #define FAST_CACHE_ALLOC_MASK         0x1ff8
    #define FAST_CACHE_ALLOC_MASK16       0x1ff0
    #define FAST_CACHE_ALLOC_DELTA16      0x0008

size_t size = _flags & FAST_CACHE_ALLOC_MASK; return align16(size + extra - FAST_CACHE_ALLOC_DELTA16); 神奇的代码,暂还没读懂啥意思~

四、init和new

当然我们在alloc创建出来一个对象后一般都会调用init方法,或者直接通过调用new方法来进行对象的创建。那它们具体是做了什么呢,看一下init和new的源码实现:

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

此处的init什么都没做,只是self返回,所以一般我们都在子类中重写init方法并调用self = [super init]
而new则先调用了callAlloc方法,然后调用init,相当于调用了alloc + init

最后

关于对象的创建流程,本文中只分析到对象大小创建这一步,关于calloc调用、isa信息初始化的分析就留着后面再继续吧~~

未完待续...

相关文章

  • 对象alloc那点事(一)

    在平常开发中我们会经常用到自定义类,并且会调用其alloc+init方法来创建一个实例对象。但是这个过程到底发生了...

  • isa构造那点事(三)

    背景 在上篇对象alloc那点事中分析对象创建过程,最终是走到_class_createInstanceFromZ...

  • ios底层原理 文章汇总

    oc对象本质:对象alloc那点事(一)[https://www.jianshu.com/p/a029af32d6...

  • 对象size那点事(二)

    背景 在上篇对象alloc那点事中遗留了一个对象带有基础类型属性后占用内存大小的问题。另外参考之前分析的对象开辟内...

  • iOS学习-OC对象的本质2

    一、OC对象的分类 1、instance 对象 what 实例对象,通过alloc出来的对象,每次alloc都会产...

  • Objective-C对象的分类

    Ojbective-C对象 instance 实例对象:就是通过类alloc出来的对象,每次调用alloc都会产生...

  • alloc init探索

    1、alloc init 首先用alloc生成一个LGPerson对象。在init另外的对象。 输出生成的对象信息...

  • iOS底层探索之对象原理(一)

    前言 对象创建alloc,alloc是iOS开发中为对象申请开辟内存的方法,那么alloc的底层到底做了哪些,以及...

  • 三、iOS中的isa

    一、isa 是什么? 通过iOS源码alloc init new 分析可以知道,对象通过alloc创建对象,分配内...

  • OC 对象 "+alloc" 方法内部流程

    示例: SomeClass 对象 alloc 方法调用 重点1. 对象调用 alloc,底层 C 接口 objc...

网友评论

      本文标题:对象alloc那点事(一)

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