天天看点

[iOS]深入理解ivar及property

以下所有类和对象的描述均以objective-c为参考, cpu架构为arm64

类本身是一个描述, 描述里包含实例化这个类需要多大的内存, 以及内存的每个byte<code>是</code>什么内容, 这个内容的头部是一个isa, 其他内容是ivar的值或指针.

对象是按类的描述所从内存空间里面开辟出对应大小的空间并填充isa指针(alloc), 类的初始化方法往这个空间里的byte里面存初始化的内容.

举个例子:

这个类被编译之后变成一个描述(arm64), aclass占用24个字节, 前8个字节是isa指针, 中间八个字节是nsstring的指针, 后八个字节是一个nsinteger的值.:

调用<code>[[aclass alloc] init]</code>在alloc的时候, 会分配24个字节的内存出来:

然后往前8个字节放isa的地址(mask之后的), alloc完成后的内存长这样:

然后调用alloc出来的这个对象的init方法, init方法会把aint的位置设为1, 而astring的位置没有做初始化, 因此还是0:

isa指向了这个类的meta, meta里面存了父类/ivar结构/方法等内容, 后续再做详解.

代码贴到xcode里面, 然后xcode -&gt; debug -&gt;debug workflow -&gt; always show disassembly

运行断点会断在这里:

[iOS]深入理解ivar及property

通过lldb的register read指令读出当前的寄存器的值:

[iOS]深入理解ivar及property

可以看到x8是alloc方法, x9是aclass. 断点下面两行指令会分别把aclass和alloc方法分别作为self和selector放到x0/x1寄存器作为objc_msgsend的第一个(self)和第二个参数(cmd).

把断点断在16行位置, 看看objc_msgsend调完之后x0的值:

[iOS]深入理解ivar及property

得到的结果是:

[iOS]深入理解ivar及property

这已经是aclass的一个对象了, 因为这里并没有调用init, 所以这个对象的状态应该是:

通过 <code>shift + command + m</code>看看内存<code>0x15d602780</code>:

[iOS]深入理解ivar及property

前8个byte是isa, 中8个和后8个byte都是0, 跟第一节中的描述一致.

在把断点断到第20行(第19行是调用init方法, 大家可以自己尝试), 看看init调用之后的内存内容:

[iOS]深入理解ivar及property

前8个byte是isa, 中8个byte是0, 后8个byte的内容是数字1, 跟第一节中的描述一致.

@property实际上是一个编译器指令, 在编译器会根据指令后的参数自动生成ivar和ivar的getter和setter.

把示例代码做一个修改, 把ivar改为property, 再看看不同属性下自定义getter和setter的不同实现.

main实现改为如下代码, 为了直观表示代码直接用getter和setter访问和赋值:

前置知识: 在oc发消息对应的方法的实现进行调用时, x0是调用方法的对象, x1是selector, 之后的x2/x3/x4...是传进来的参数(如果有参数). 方法调用完成后, 返回值放在x0(如果有返回值). 注意, 这段描述并不完全准确也不完整, 具体请参考苹果官方文档[1].

在用hopper disassembler分析下编译产物, 先来setter'-[aclass setastring:]'.

上面代码干的事情, 就是把要赋值的对象存一份到对象的ivar偏移量对应的位置. 同时strong属性这里会通过objc_storestrong[2]把引用计数+1.

再来setter<code>-[aclass astring]</code>:

上面代码干的事情, 就是把内容从对象的ivar偏移量所在的位置取出来.

在来看看另一个属性, 还是先来setter<code>-[aclass setaint:]</code>:

由于nsinteger本身不是对象没有引用计数等操作, 这里的代码比较简单, getter与上面的getter也类似就不做额外解析了.

strong和weak和assign的区别在于objc_storestrong和objc_storeweak和直接赋值.