OC对象底层原理探究

2019-03-10  本文已影响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++ 中不必要的时间和运算。
验证方法:

int sum(int a, int b){
   return a+b;
}
int main(int argc, char * argv[]) {
   int c = sum(10,20);
}

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

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

上一篇下一篇

猜你喜欢

热点阅读