属性&成员变量
这里借助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
,具体查找流程请戳这