alloc对象源码分析-上篇

930 阅读6分钟

前言

每天都在使用对象,创建对象。但并知道苹果底层是怎样去创建的对象,alloc方法是实现是怎么样。这次决定静下来心来,探究一下苹果的底层代码

目标

  1. 我们要找到alloc的源码调用顺序是怎么样?

第一步 --- 要先学会调试办法,找到入口,一步步探究下去。

准备工作:我们创建一个新的ios项目,在ViewController.m的viewDidLoad方法中写以下代码(此篇文章所涉及到代码都基于arm64真机进行调试

    LGPerson *p1 = [LGPerson alloc];
    LGPerson *p2 = [p1 init];
    LGPerson *p3 = [p1 init];

    NSLog(@"%@-%p-%p",p1,p1,&p1);
    NSLog(@"%@-%p-%p",p2,p2,&p2);
    NSLog(@"%@-%p-%p",p3,p3,&p3);

方法-1.我们先通过断点的方式,control+stepInfo 进入方法内部

截屏2021-08-31 下午9.54.32.png

一步步往下走,又看到一些有意思的函数,dyld_stub_binder 这是干什么得呢,先留在这里后面我百度看看,看名称就是动态库做什么绑定操作,我们继续往下走

截屏2021-08-31 下午9.57.13.png

然后继续往下走,我们又发现了objc_msgSend

截屏2021-08-31 下午10.01.06.png

哎呀 sorry,我断点打在了init函数那行,stepInfo进入得是init函数的底层触发,重新在alloc行进行断点

截屏2021-09-01 上午10.14.55.png

这时候我们看到调用了objc_alloc方法,继续往下走走看

截屏2021-09-01 上午10.16.59.png

我们又发现了_objc_rootAllocWithZone和obj_msgSend.

方法-2.我们通过汇编进行调试,我们也可以快速定位到了objc_alloc的方法

截屏2021-09-01 上午10.20.05.png

截屏2021-09-01 上午10.22.15.png

方法-3.通过增加符号断点,增加一个alloc的符号断点,我们可以看到

截屏2021-09-01 上午10.26.50.png

截屏2021-09-01 上午10.28.11.png

第二步 追踪alloc流程

方法掌握了,我们开始探究,首页我们运行一下代码,看一下打印结果

**2021-09-01 10:27:27.374802+0800 001-alloc&init探索[368:122896] <LGPerson: 0x283f04000>-0x283f04000-0x16ced1c08**
**2021-09-01 10:27:33.671104+0800 001-alloc&init探索[368:122896] <LGPerson: 0x283f04000>-0x283f04000-0x16ced1c00**
**2021-09-01 10:28:27.939738+0800 001-alloc&init探索[368:122896] <LGPerson: 0x283f04000>-0x283f04000-0x16ced1bf8**

结论:p1 p2 p3 内存中的对象地址是一样的,但指针地址完全不一样

扩展一下:

1.指针存在stack栈区,对象存在heap堆区 2.栈区由系统申请,开辟地址由大到小,堆区由程序员去申请,开辟地址由小到大。 3.另外我们还有静态变量区,常量字符串区,代码区 4.我们开辟的对象内存地址,其实是虚拟内存地址,并不是真正的内存地址,我们是没有办法知道真正的内存地址。

好了,我们要开始追溯alloc的流程,我们现在定位到了alloc会调用objc_alloc方法,那我们就要进入到读源码的环节[objc4-818.2],苹果开放了蛮多的源码,对于我们学习还是很有帮助的,大家应该去看看源码,学习大神们的思路。

截屏2021-09-01 上午11.24.08.png

callAlloc方法看看

核心代码内容
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));
}

_class_createInstanceFromZone看看

_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);//1.计算申请内存空间
    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);//2.开辟空间
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }
    if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor);//3.初始化ISA和cls关联起来
    } 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);
}

看看instanceSize(extraBytes)如何实现

inline size_t instanceSize(size_t extraBytes) const {
        //快速计算内存大小
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(extraBytes);
        }
        //计算类中所有变量需要的内存大小   extraBytes额外字节数一般是0
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        //最小返回16字节
        if (size < 16) size = 16;  
        return size;
    }

再往里去探究,我们发现了这个方法,就算出对象的size后,做了一个16字节对齐.

static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}

开辟好了内存后,继续往下但是这里会发现一个问题这里打印的obj的地址和我们之前打印p1 p2的地址格式并不相同,我们先继续往下走进入到initInstanceIsa方法中

截屏2021-09-01 下午1.49.34.png

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

发现初始化是initIsa方法干的时候,我们就继续跟进去,这个时候我们发现了一个isa_t,这是一个联合体,有属性互斥的特征

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    uintptr_t bits;
private:
    // Accessing the class requires custom ptrauth operations, so
    // force clients to go through setClass/getClass by making this
    // private.
    Class cls;
public:
#if defined(ISA_BITFIELD)  //这里是我们重点要看的一个宏定义
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
    bool isDeallocating() {
        return extra_rc == 0 && has_sidetable_rc == 0;
    }
    void setDeallocating() {
        extra_rc = 0;
        has_sidetable_rc = 0;
    }
#endif
    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated);
    Class getDecodedClass(bool authenticated);
};

我们看到了一个宏ISA_BITFIELD,这个isa_t存了比较多重要的信息,我们一起看一下

#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \ 类信息
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \弱引用
      uintptr_t unused            : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8                                           计数器

之后就返回了obj对象了。一个调用的流程就基本走完。后面再对整个流程的知识点独立分析看看 在这个流程中我们遇到了一个问题,通过源码断点,我们发现对象的alloc方法走的是,这个部分是LLVM做了一些优化,苹果的目的应该是对于内存申请做了一些标记和监控。

static void 
fixupMessageRef(message_ref_t *msg)
{    
    msg->sel = sel_registerName((const char *)msg->sel);
    if (msg->imp == &objc_msgSend_fixup) { 
        if (msg->sel == @selector(alloc)) {
            msg->imp = (IMP)&objc_alloc;
        } else if (msg->sel == @selector(allocWithZone:)) {
            msg->imp = (IMP)&objc_allocWithZone;
        } else if (msg->sel == @selector(retain)) {
            msg->imp = (IMP)&objc_retain;
        } else if (msg->sel == @selector(release)) {
            msg->imp = (IMP)&objc_release;
        } else if (msg->sel == @selector(autorelease)) {
            msg->imp = (IMP)&objc_autorelease;
        } else {
            msg->imp = &objc_msgSend_fixedup;
        }
    }

第三步 内存分析(对象大小空间)

LGPerson *person = [LGPerson alloc];
person.name      = @"stone";
person.nickName  = @"ios";
NSLog(@"对象类型的内存大小--%lu",sizeof(person));
NSLog(@"对象实际的内存大小--%lu",class_getInstanceSize([person class]));
NSLog(@"系统分配的内存大小--%lu",malloc_size((__bridge const void *)(person)));

2021-09-01 16:04:36.477765+0800 001-内存对齐原则[404:180545] 对象类型的内存大小--8
2021-09-01 16:04:36.478278+0800 001-内存对齐原则[404:180545] 对象实际的内存大小--24
2021-09-01 16:04:36.478312+0800 001-内存对齐原则[404:180545] 系统分配的内存大小--32

结论: 1.sizeof 是返回类型的size 例如double占8字节,int4字节,void(*)代表指针占8字节,对象这个类型也是占8个字节 2.class_getInstanceSize 动态计算对象size Person 24个字节 其实isa占8个+name占8个字节+nickName占8个字节 3.malloc_size系统开辟的内存空间,按照16个字节进行对齐,要存下24个字节的person,需要开辟32个字节的空间

不同类型占用内存的情况

27b1891427a4494baaec430175393223~tplv-k3u1fbpfcp-watermark.image.jpg

未完待续:

第四步 对象本质 nonPoiterIsa