美文网首页iOS 底层原理iOS 底层分析
iOS底层探索之对象原理(一)

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

作者: litongde | 来源:发表于2019-12-23 14:15 被阅读0次

    前言

    对象创建alloc,alloc是iOS开发中为对象申请开辟内存的方法,那么alloc的底层到底做了哪些,以及alloc是如何申请并且开辟内存的,下面和大家一起探索一下alloc的具体步骤。

    alloc探索思路

    以下是三种常用的探索手法,也可以跳过直接从alloc底层原理看起

    TDPerson *p1 = [TDPerson alloc];
    TDPerson *p2 = [p1 init];
    TDPerson *p3 = [p1 init];
    
    NSLog(@"%@ —— %p", p1, &p1)
    NSLog(@"%@ —— %p", p2, &p2)
    NSLog(@"%@ —— %p", p3, &p3)
    
    输出:
    <TDPerson: 0x6000032f0218> —— 0x6000032f038
    <TDPerson: 0x6000032f0218> —— 0x6000032f030
    <TDPerson: 0x6000032f0218> —— 0x6000032f028
    

    1、下断点

    control + in 找到 libobjc.A.dylib`objc_alloc (注: 建议用真机,模拟器走x86,真机走的arm64)

    2、下符号断点(Symbolic Breakpoint)

    断点 alloc { 找到 libobjc.A.dylib`+[NSObject alloc]

    3、汇编

    打开汇编查看流程 Debug ——> Workflow ——> always show Disassembly

    alloc底层原理

    上面比较难断点跟踪,为了更加直观的看到objc源码中的方法调用,方便我们断点跟踪,因此配置出了可直接编译运行的源码,配置如:objc4-750源码 + Xcode11 + MacOS 10.15 ios最新支持ios10.14
    官方源码下载地址
    iOS_objc4-756.2 最新源码编译调试

    代码准备好,打上断点,command+左键 开始追踪alloc方法,进入如下流程:

    • alloc
    + (id)alloc {
        return _objc_rootAlloc(self);
    }
    
    • _objc_rootAlloc
    _objc_rootAlloc(Class cls)  {
        return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
    }
    
    • callAlloc
    static ALWAYS_INLINE id
    callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
    {
        if (slowpath(checkNil && !cls)) return nil;
    #if __OBJC2__
        if (fastpath(!cls->ISA()->hasCustomAWZ())) {
            // canAllocFast返回值固定为false,内部调用了一个bits.canAllocFast, 所以创建对象暂时看来只能用到else中的代码
            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
        if (allocWithZone) return [cls allocWithZone:nil];
        return [cls alloc];
    }
    
    • class_createInstance
    id 
    class_createInstance(Class cls, size_t extraBytes) {
        return _class_createInstanceFromZone(cls, extraBytes, nil);
    }
    
    • _class_createInstanceFromZone

    在_class_createInstanceFromZone中进行对象size计算instanceSize,内存申请calloc,以及isa初始化initInstanceIsa

    _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, \
                                  bool cxxConstruct = true, size_t *outAllocatedSize = nil)
    {
        /* 省略代码 */
        size_t size = cls->instanceSize(extraBytes);     // 对象size计算
        if (outAllocatedSize) *outAllocatedSize = size;
    
        id obj;
        if (!zone && fast) { 
            obj = (id)calloc(1, size);                  // 开辟了内存空间
            if (!obj) return nil;
            obj->initInstanceIsa(cls, hasCxxDtor)       // 让obj和当前的class关联起来
        } else {
            if (zone) {
                obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
            } else {
                obj = (id)calloc(1, size);
            }
            if (!obj) return nil;
            // Use raw pointer isa on the assumption that they might be 
            // doing something weird with the zone or RR.
            obj->initIsa(cls);
        }
        /* 省略代码 */
    }
    

    alloc申请内存(字节对齐)

    size_t size = cls->instanceSize(extraBytes); size是如何开辟内存空间呢

    1. 我们对象需要的内存空间 以 8位倍数进行存储 以 8字节对齐
    2. 最少16字节

    好处:方便计算机读取,以空间换取时间

    // 申请地址,此处有16字节限制
    size_t instanceSize(size_t extraBytes) { 
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }
    
    // 此方法,用来字节对齐
    uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize()); 
    }
    
    // 字节对齐的详细方法,WORD_MASK在64位下为7,32位下为3,用来进行字节对齐
    static inline size_t word_align(size_t x) {
        return (x + WORD_MASK) & ~WORD_MASK;
    }
    

    小扩展

    init方法

    查看init源码,init 直接 return obj; 规范代码,工厂设计模式,交给子类去自定义重写,没有其他实质功能作用

    - (id)init {
        return _objc_rootInit(self);
    }
    
    new方法

    查看new源码,内部调用callAlloc 后执行init,得出结论:new = alloc + init

    + (id)new {
        return [callAlloc(self, false/*checkNil*/) init];
    }
    
    对象属性在内存中的布局

    如何查看对象的存储空间 内存 run,常用LLDB命令如:

    • register read:读寄存器;x0即是第一个参数的传递者,也是返回值的存储地方。
    • x object:以16进制打印这个对象的地址空间,iOS是小端模式 , 内存 run 是反的。
    • x/4xg object:以16进制的方式打印 object 的 4 段内存区域的地址,每一段是 8 个字节大小。

    更多LLDB命令可移步参考iOS之LLDB常用命令

    附上alloc流程图

    alloc流程图.png

    相关文章

      网友评论

        本文标题:iOS底层探索之对象原理(一)

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