聊一聊@property中的strong和weak到底做了什么

1,496 阅读2分钟

环境说明: 从githud 上下载一份编译好的runtime源码(我使用的是objc-818版本)

首先创建一个ELPerson类,添加一个strong修饰的属性strongPerson(NSObject类型)

为了便于观察给strongPerson变量赋值时,系统调用了哪些方法,我添加了一个personConfig的方法,在该方法中为strongPerson赋值。

image.png

image.png

在main方法中创建ELPerson对象,同时打印改对象的地址,调用对象的personConfig方法

image.png

将ELPerson.m 文件使用clang命令转成.cpp查看系统实现 ( clang -rewrite-objc ELPerson.m)

image.png

运行程序,并使用LLDB指令(breakpoint set -n personConfig)将断点设置在personConfig方法里

image.png

image.png

这时可以查看到personConfig的汇编实现。

image.png

从注释可以看出来,当给strongPerson赋值时,会调用setStrongPerson方法。

继续将断点设置在setStrongPerson:方法中 ( breakpoint set -n setStrongPerson:)

image.png

image.png

此时可查看一下ELPerson.cpp中的c++实现。 搜索 setStrongPerson: 在ELPerson 的方法列表中可以查看到以下方法,

image.png

image.png

self + OBJC_IVAR_$_ELPerson$_strongPerson)) = strongPerson;

// OBJC_IVAR_$_ELPerson$_strongPerson 实现
extern "C" unsigned long int OBJC_IVAR_$_ELPerson$_strongPerson __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct ELPerson, _strongPerson);

#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)

__OFFSETOFIVAR__ 这个宏定义是取到_strongPerson 在结构体ELPerson中的偏移量,知道偏移量self + OBJC_IVAR_$_ELPerson$_strongPerson 就可以得到_strongPerson的需要存储的地址位置

对应到oc代码,就是将strongPerson的值赋值给_strongPerson

NSObject * strongPerson = [[NSObject alloc]init];
self.strongPerson = strongPerson;

对应到汇编代码,

    0x100003e20 <+0>:  pushq  %rbp
    0x100003e21 <+1>:  movq   %rsp, %rbp
    0x100003e24 <+4>:  subq   $0x20, %rsp
    0x100003e28 <+8>:  movq   %rdi, -0x10(%rbp)
    0x100003e2c <+12>: movq   %rsi, -0x18(%rbp)
    0x100003e30 <+16>: movq   %rdx, -0x8(%rbp)
    0x100003e34 <+20>: movq   -0x8(%rbp), %rsi
    0x100003e38 <+24>: movq   -0x10(%rbp), %rdi
    0x100003e3c <+28>: addq   $0x8, %rdi
    0x100003e43 <+35>: callq  0x100003e96               ; symbol stub for: objc_storeStrong
    0x100003e48 <+40>: addq   $0x20, %rsp
->  0x100003e4c <+44>: popq   %rbp

image.png

image.png

通过指令读取寄存器中的值: rdi 中存储的是 ELPerson 对象的地址 rsi 中存储的是 strongPerson 对象的地址

addq 0x8,0x8, %rdi 是将 ELPerson 对象的地址加8,8是strongPerson占用的内存空间大小。就相当于之前的c++代码 self + OBJC_IVAR__ELPerson$_strongPerson

找到变量 _strongPerson 在结构体中需要存储的位置,调用 objc_storeStrong 方法,并进行赋值。

image.png

断点到 objc_storeStrong 方法中:

image.png

location 是self+偏移量的地址 (self 是ELPerson 对象类型) obj 是创建出来的strongPerson对象

objc_retain(obj); 先对strongPerson做了retain操作, *location = obj; 然后将strongPerson的值 赋值到 location 这块地址中 objc_release(prev); 释放location地址中旧的值

验证一下:

image.png

至此setStrongPerson 方法调用完毕。

同理,创建weak属性的weakPerson属性,并在personConfig为其赋值。

image.png

image.png

将断点设置在setWeakPerson:方法中 (breakpoint set -n setWeakPerson: )

image.png

可以看到setWeakPerson方法中调用的是objc_storeWeak 方法进行赋值操作 image.png

将断点设置在objc_storeWeak方法中:

image.png

location 是self+偏移量的地址 (self 是ELPerson 对象类型) obj 是创建出来的weakPerson对象

继续调用storeWeak方法

image.png

image.png

概括来讲同样是释放旧值,赋值新值到location地址中的操作。

与strong不同的是,不会增加对象的强引用,而是加入到弱引用表中。