美文网首页
OC对象底层原理探究

OC对象底层原理探究

作者: 蚂蚁也疯狂 | 来源:发表于2019-03-10 16:14 被阅读0次

    1.runtime 是什么?
    回答:runtime是由C C++ 汇编为oc提供的一套运行时的api

    2.以下代码输出地址一样吗? 为什么?

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

    回答:他们的内存地址是一样的。至于为什么,我们一起来学一学。(😁)

    1. alloc init 底层到底做了什么?

    问题:1.苹果是闭源的,你是怎么去看他的源码呢?
    答:其实苹果也开源了一部分东西,具体在苹果开源网站,可以选择当前的系统版本,配置参考objc4-750文档

    问题:2.你是怎么知道alloc这个方法在哪个库中?
    答:
    1.通过汇编分析。(由于对汇编不是太了解,代码调试我选择真机调试,个人觉得真机汇编比模拟器少部分指令)
    2.配置可编译的objc4代码

    汇编分析

    当我们不知道要使用的库在哪里时?我们怎么去找对应的库与资料呢?
    1.汇编分析:第一步:新建项目,创建自定义类继承自NSObject

    1. 做如下操作:
    /*查找步骤:
         注意:这里提供的是我自己运行的方法,验证的同学可以多操作几遍,重新验证需要 command + shift + k ,否则编译器 优化,部分步骤不走
         1. 在 alloc 方法上面下断点
         2. 当项目走到这一步的时候进行 Debug -> debug workflow -> always show ..
         3. 跳转到汇编界面 向下走3步,看到 bl指令,打个断点放到bl指令上,走到断点的地方
        
         4. 按住 control 点击向下走一步的图标,进入到汇编 objc_msgSend()
         5. 此时添加 符号断点 alloc,点击运行 --> libobjc,此时 会看到 _objc_rootAlloc函数 ,此时可以对照 objc750源码看。
         6. 点击 走一步 图标 会看到 汇编 class_createInstance 函数
         7. 汇编往下走(这里需要按住 control 并且点击走一步按钮),知道 可以 看到 __NSUnrecognizedTaggedPointer 这个类型时
         8. 这里注意下 打断点 看 ret 指令之前的寄存器内容,在 createInstance 函数之前 寄存器x0 是没有 内容的。
         9.小伙伴们如果有耐心的话可以用汇编的方法调试下当前的alloc创建流程,我懒,所以我用objc源码调试。
         10.alloc 调用流程:
    
    alloc流程.png

    注意:上面这张图是被优化之后的调用流程,原始调用流程如下图(cooci的图片)

    alloc流程图.png

    2.init 底层探索(基于objc750探索)

    还是在之前的项目中

    探索步骤:
         1.在 init 方法打断点
         2.当项目走到这一步的时候进行 Debug -> debug workflow -> always show ..
         3.找到 bl objc_msgSend 并且运行到这一步
         4.按住 control 点击向下走一步的图标,进入到汇编 objc_msgSend()
         5.添加 init符号断点,发现 init 在 libobjc.A.dylib这个动态库中
         6.读取寄存器 register read 可以看到 init方法 。为了方便,我们直接去源码查看
    
    读取寄存器状态 register read.png init 源码调用图片: init1.png init2.png

    这里发现init中直接返回的是 self 自己,是苹果的一个重要的设计模式,这里可以根据自己的需求去初始化一些属性。

    3.LLVM的优化

    apple 开发者去掉了 一些 连接 编译 运行 空闲 C++ 中不必要的时间和运算。
    验证方法:

    • 创建一个 mac 命令行工程
    • 添加以下代码,同样使用汇编进行调试。注意汇编代码行数
    int sum(int a, int b){
       return a+b;
    }
    int main(int argc, char * argv[]) {
       int c = sum(10,20);
    }
    
    • 设置以下操作,然后再次进行汇编调试,会发现汇编代码少了许多


      image.png

    4.分析 alloc 被优化部分

    alloc ->_objc_rootAlloc -> callAlloc -> 
    class_createInstance -> _class_createInstanceFromZone 
    

    主要查看: _class_createInstanceFromZone函数,这里有对象初始化的一些列内容。
    涉及到系统内存对齐(变量 8的倍数),系统内存对齐(16的倍数)

    测试系统内存对齐:
    
    
    niecun.png

    问题:我有Person对象,有name 和 age 属性,请问 persion 中占用多少内存?
    根据测试结果如图:


    neicun2.png

    分析:
    1. name 长度为 8位,age为int类型,长度为 4位 内存对齐之后为 16位,为什么会显示 24呢?
    2. 通过配置的objc-750 我们看到了 如下函数

    static __attribute__((always_inline)) 
    id
    _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                                  bool cxxConstruct = true, 
                                  size_t *outAllocatedSize = nil)
    {
        if (!cls) return nil;
    
        assert(cls->isRealized());
    
        // Read class's info bits all at once for performance
        bool hasCxxCtor = cls->hasCxxCtor();
        bool hasCxxDtor = cls->hasCxxDtor();
        bool fast = cls->canAllocNonpointer();
    
        size_t size = cls->instanceSize(extraBytes);
        if (outAllocatedSize) *outAllocatedSize = size;
    
        id obj;
        if (!zone  &&  fast) {
            obj = (id)calloc(1, size);
            if (!obj) return nil;
            obj->initInstanceIsa(cls, hasCxxDtor);
        } 
        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);
        }
    
        if (cxxConstruct && hasCxxCtor) {
            obj = _objc_constructOrFree(obj, cls);
        }
    
        return obj;
    }
    
    
    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;
    }
    

    在 instanceSize 中我们也可以看到 size 最小为 16位
    其中:initInstanceIsa 函数为对象添加了isa 指针,长度为8位。也就是说我们的对象内存计算方式为: name(8) + age(4) + isa(8)
    所以最后对齐为 24位。

    可能有些同学会有疑惑,你说isa是8位,你能给我打印出来,或者是用lldb调试出来让我看看嘛?
    后面再说,关于 objc4-750 源码配置我们可以看看objc4-750配置 使用源码调试 isa问题。

    文章对应测试代码测试代码地址 文件夹为 Allock&init

    文章写的不好,请见谅。如有问题,欢迎指正。

    相关文章

      网友评论

          本文标题:OC对象底层原理探究

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