属性&成员变量
这里借助Clang探索属性和成员变量在底层的表现形式,用Clang编译以下代码:
@interface LRPerson : NSObject
{
NSString *hobby;
NSObject *obj;
}
@property (nonatomic,copy) NSString *name;
@property (nonatomic,strong) NSString *nickName;
@end
@implementation LRPerson
@end
- 打开main.cpp文件,搜索
LRPerson,找到结构体LRPerson_IMPL
extern "C" unsigned long OBJC_IVAR_$_LRPerson$_name;
extern "C" unsigned long OBJC_IVAR_$_LRPerson$_nickName;
struct LRPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *hobby;
NSObject *obj;
NSString *_name;
NSString *_nickName;
};
static NSString * _I_LRPerson_name(LRPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LRPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_LRPerson_setName_(LRPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LRPerson, _name), (id)name, 0, 1); }
static NSString * _I_LRPerson_nickName(LRPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LRPerson$_nickName)); }
static void _I_LRPerson_setNickName_(LRPerson * self, SEL _cmd, NSString *nickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_LRPerson$_nickName)) = nickName; }
由此可知属性在底层,也被加在了成员变量里,区别只是成员变量没有setter和getter方法,属性 = 下划线成员变量 + setter + getter
观察上面的setter和getter方法,我们还有一些疑问:
属性如何生成setter和getter方法?
用strong修饰的nickName的setter方法为何没有走objc_setProperty?
思考:编译期就已经生成了
setter方法,故应该去llvm里面寻找答案
llvm分析setter方法
打开llvm源码,搜索objc_setProperty在CGObjcMac.cpp可以找到以下代码

llvm根据属性是否有修饰符atomic、copy分别调用objc_setProperty_xx方法
那么weak和strong修饰的属性又是如何处理的呢?
我们在strong修饰的nickName代码处打上断点,用control+step into的方式调试

strong修饰的属性的setter方法最终走了objc_storeStrong方法
void objc_storeStrong(id *location, id obj)
{
id prev = *location; //旧值
if (obj == prev) {
return; //新值等于旧值 直接return
}
objc_retain(obj); //新值retain
*location = obj; //赋值
objc_release(prev); // 旧值release
}
对应的weak修饰的最终走objc_storeWeak方法
id
objc_storeWeak(id *location, id newObj)
{
return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object *)newObj);
}
实例变量
实例变量是对象类型具备实例化的成员变量,例如代码中的成员变量NSObject *obj;,obj就是一个实例变量。实例变量是一种特殊的成员变量
属性&成员变量存储位置
属性存在rw的property_array_t,成员变量存储在ro的ivars里面。下面用LLDB一一验证。
- 取到
class_rw_t
(lldb) x/4gx p.class
0x1000022e8: 0x00000001000022c0 0x00000001003f0140
0x1000022f8: 0x0000000101104b70 0x0002803400000003
(lldb) p (class_data_bits_t *)0x100002308
(class_data_bits_t *) $1 = 0x0000000100002308
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001012b91d0
(lldb)
- 取到
class_ro_t
(lldb) p $2->ro()
(const class_ro_t *) $3 = 0x0000000100002088
(lldb)
- 取
property_list_t
(lldb) p $2->properties()
(const property_array_t) $4 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x00000001000021d8
arrayAndFlag = 4294975960
}
}
}
(lldb) p $4.list
(property_list_t *const) $5 = 0x00000001000021d8
(lldb)
- 查看
property_list_t
(lldb) p *$5
(property_list_t) $6 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 2
first = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
}
}
(lldb)
- 遍历
property_list_t
(lldb) p $6.get(0)
(property_t) $7 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $6.get(1)
(property_t) $8 = (name = "nickName", attributes = "T@\"NSString\",&,N,V_nickName")
(lldb) p $6.get(2)
error:越界
属性name和nickName在property_list_t已全部找到。下面前往ro中验证成员变量的存储位置
- 取
ivars
(lldb) p $3->ivars
(const ivar_list_t *const) $9 = 0x0000000100002150
(lldb) p *$9
(const ivar_list_t) $10 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 4
first = {
offset = 0x00000001000022a0
name = 0x0000000100000f23 "hobby"
type = 0x0000000100000f6f "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
(lldb)
- 遍历
ivar_list_t,count为4
(lldb) p $10.get(0)
(ivar_t) $11 = {
offset = 0x00000001000022a0
name = 0x0000000100000f23 "hobby"
type = 0x0000000100000f6f "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $10.get(1)
(ivar_t) $12 = {
offset = 0x00000001000022a8
name = 0x0000000100000f29 "obj"
type = 0x0000000100000f7b "@\"NSObject\""
alignment_raw = 3
size = 8
}
(lldb) p $10.get(2)
(ivar_t) $13 = {
offset = 0x00000001000022b0
name = 0x0000000100000f2d "_name"
type = 0x0000000100000f6f "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $10.get(3)
(ivar_t) $14 = {
offset = 0x00000001000022b8
name = 0x0000000100000f33 "_nickName"
type = 0x0000000100000f6f "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb)
4个成员变量均在其中,2个原本就是成员变量,另外两个是属性加下划线生成的。
方法编码
clang的main.cpp文件中可以找到相应的method_list,
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[4];
} _OBJC_$_INSTANCE_METHODS_LRPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
4,
{{(struct objc_selector *)"name", "@16@0:8", (void *)_I_LRPerson_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LRPerson_setName_},
{(struct objc_selector *)"nickName", "@16@0:8", (void *)_I_LRPerson_nickName},
{(struct objc_selector *)"setNickName:", "v24@0:8@16", (void *)_I_LRPerson_setNickName_}}
};
上文中多次出现的@16@0:8,v24@0:8@16,这些数字和符号又是啥意思呢?
这其实是方法签名,苹果官方文档提供了相应的编码表。command+shift+0->搜索ivar_getTypeEncoding打开官方文档

我们把setName方法和签名对应起来看
static void _I_LRPerson_setName_(LRPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LRPerson, _name), (id)name, 0, 1); }
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LRPerson_setName_}
每个方法都会有两个默认的参数,self和SEL, setName方法在底层实际上传了3个参数,LRPerson * self, SEL _cmd, NSString *name
v24@0:8@16的每一个字符都有相对的含义
-
v:表示函数的返回值是void -
24:表示函数的所有参数占用24字节 -
@:表示第一个参数是id类型 -
0:表示第一个参数从0号位置开始存 -
::表示第二个参数是sel -
8:表示第二个参数是从8号位置开始存 -
@:表示第三个参数是id类型 -
16:表示第三个参数从16号位置开始存
属性的编码 (Property Attribute)
clang的main.cpp文件中可以找到相应的_prop_list_t,
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_LRPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,
{{"name","T@\"NSString\",C,N,V_name"},
{"nickName","T@\"NSString\",&,N,V_nickName"}}
};
这些T@\"NSString\",C,N,V_name是方法的Attribute,command+shift+0->搜索property_getAttributes打开官方文档
T@\"NSString\",C,N,V_name"
T:类型为@"NSString",其中\是转义字符
C:表示copy
N:表示nonatomic
V: 表示varible,真的变量名_name
sel 和 imp
sel: sel只是一个方法编号
imp: imp一个函数指针,保存了方法的地址
sel 和 imp 的关系:
sel通过方法查找流程可以拿到imp,具体查找流程请戳这