iOS底层-10:成员变量与属性的存储

属性&成员变量

这里借助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; }

由此可知属性在底层,也被加在了成员变量里,区别只是成员变量没有settergetter方法,属性 = 下划线成员变量 + setter + getter

观察上面的settergetter方法,我们还有一些疑问:

属性如何生成settergetter方法?
strong修饰的nickNamesetter方法为何没有走objc_setProperty?

思考:编译期就已经生成了setter方法,故应该去llvm里面寻找答案

llvm分析setter方法

打开llvm源码,搜索objc_setPropertyCGObjcMac.cpp可以找到以下代码

image.png

llvm根据属性是否有修饰符atomiccopy分别调用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就是一个实例变量。实例变量是一种特殊的成员变量

属性&成员变量存储位置

属性存在rwproperty_array_t,成员变量存储在roivars里面。下面用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:越界

属性namenickNameproperty_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_tcount为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个原本就是成员变量,另外两个是属性加下划线生成的。

方法编码

clangmain.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:8v24@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_}

每个方法都会有两个默认的参数,selfSELsetName方法在底层实际上传了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)

clangmain.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是方法的Attributecommand+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,具体查找流程请戳这

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容