博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
刨根问底Objective-C Runtime(4)- 成员变量与属性
阅读量:5310 次
发布时间:2019-06-14

本文共 8359 字,大约阅读时间需要 27 分钟。

上一篇笔记讲述了,本篇笔记主要是讲述objc runtime的 成员变量属性

习题内容

下面代码会? Compile Error / Runtime Crash / NSLog…?

@interface Sark : NSObject@property (nonatomic, copy) NSString *name;@end@implementation Sark- (void)speak{    NSLog(@"my name is %@", self.name);}@end@interface Test : NSObject@end@implementation Test- (instancetype)init{    self = [super init];    if (self) {        id cls = [Sark class];        void *obj = &cls;        [(__bridge id)obj speak];    }    return self;}@endint main(int argc, const char * argv[]) {    @autoreleasepool {        [[Test alloc] init];    }    return 0;}

答案:代码正常输出,输出结果为:

2014-11-07 14:08:25.698 Test[1097:57255] my name is 

为什么呢?

前几节博文中多次讲到了objc_class结构体,今天我们再拿出来看一下:

struct objc_class {    Class isa  OBJC_ISA_AVAILABILITY;#if !__OBJC2__    Class super_class                                        OBJC2_UNAVAILABLE;    const char *name                                         OBJC2_UNAVAILABLE;    long version                                             OBJC2_UNAVAILABLE;    long info                                                OBJC2_UNAVAILABLE;    long instance_size                                       OBJC2_UNAVAILABLE;    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;#endif} OBJC2_UNAVAILABLE;

其中objc_ivar_list结构体存储着objc_ivar数组列表,而objc_ivar结构体存储了类的单个成员变量的信息。

那么什么是Ivar呢?

Ivar 在objc中被定义为:

typedef struct objc_ivar *Ivar;

它是一个指向objc_ivar结构体的指针,结构体有如下定义:

struct objc_ivar {    char *ivar_name                                          OBJC2_UNAVAILABLE;    char *ivar_type                                          OBJC2_UNAVAILABLE;    int ivar_offset                                          OBJC2_UNAVAILABLE;#ifdef __LP64__    int space                                                OBJC2_UNAVAILABLE;#endif}                                                            OBJC2_UNAVAILABLE;

这里我们注意第三个成员 ivar_offset。它表示基地址偏移字节。

在编译我们的类时,编译器生成了一个 ivar布局,显示了在类中从哪可以访问我们的 ivars 。看下图:

上图中,左侧的数据就是地址偏移字节,我们对 ivar 的访问就可以通过 对象地址 + ivar偏移字节的方法。但是这又引发一个问题,看下图:

我们增加了父类的ivar,这个时候布局就出错了,我们就不得不重新编译子类来恢复兼容性。

而Objective-C Runtime中使用了Non Fragile ivars,看下图:

使用Non Fragile ivars时,Runtime会进行检测来调整类中新增的ivar的偏移量。 这样我们就可以通过 对象地址 + 基类大小 + ivar偏移字节的方法来计算出ivar相应的地址,并访问到相应的ivar。

我们来看一个例子:

@interface Student : NSObject{    @private    NSInteger age;}@end@implementation Student- (NSString *)description{    return [NSString stringWithFormat:@"age = %d", age];}@endint main(int argc, const char * argv[]) {    @autoreleasepool {        Student *student = [[Student alloc] init];        student->age = 24;    }    return 0;}

上述代码,Student有两个被标记为private的ivar,这个时候当我们使用 -> 访问时,编译器会报错。那么我们如何设置一个被标记为private的ivar的值呢?

通过上面的描述,我们知道ivar是通过计算字节偏量来确定地址,并访问的。我们可以改成这样:

@interface Student : NSObject{    @private    int age;}@end@implementation Student- (NSString *)description{    NSLog(@"current pointer = %p", self);    NSLog(@"age pointer = %p", &age);    return [NSString stringWithFormat:@"age = %d", age];}@endint main(int argc, const char * argv[]) {    @autoreleasepool {        Student *student = [[Student alloc] init];        Ivar age_ivar = class_getInstanceVariable(object_getClass(student), "age");        int *age_pointer = (int *)((__bridge void *)(student) + ivar_getOffset(age_ivar));        NSLog(@"age ivar offset = %td", ivar_getOffset(age_ivar));        *age_pointer = 10;        NSLog(@"%@", student);    }    return 0;}

上述代码的输出结果为:

2014-11-08 18:24:38.892 Test[4143:466864] age ivar offset = 82014-11-08 18:24:38.893 Test[4143:466864] current pointer = 0x1001002d02014-11-08 18:24:38.893 Test[4143:466864] age pointer = 0x1001002d82014-11-08 18:24:38.894 Test[4143:466864] age = 10

我们可以清晰的看到指针地址的变化和偏移量,和我们上述描述一致。

说完了Ivar, 那Property又是怎么样的呢?

使用clang -rewrite-objc main.m重写题目中的代码,我们发现Sark类中的name属性被转换成了如下代码:

struct Sark_IMPL {    struct NSObject_IMPL NSObject_IVARS;    NSString *_name;};// @property (nonatomic, copy) NSString *name;/* @end */// @implementation Sarkstatic NSString * _I_Sark_name(Sark * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Sark$_name)); }static void _I_Sark_setName_(Sark * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Sark, _name), (id)name, 0, 1); }

类中的Property属性被编译器转换成了Ivar,并且自动添加了我们熟悉的SetGet方法。

我们这个时候回头看一下objc_class结构体中的内容,并没有发现用来专门记录Property的list。我们翻开objc源代码,在objc-runtime-new.h中,发现最终还是会通过在class_ro_t结构体中使用property_list_t存储对应的propertyies。

而在刚刚重写的代码中,我们可以找到这个property_list_t:

static struct /*_prop_list_t*/ {    unsigned int entsize;  // sizeof(struct _prop_t)    unsigned int count_of_properties;    struct _prop_t prop_list[1];    } _OBJC_$_PROP_LIST_Sark __attribute__ ((used, section ("__DATA,__objc_const"))) = {        sizeof(_prop_t),        1,        name};static struct _class_ro_t _OBJC_CLASS_RO_$_Sark __attribute__ ((used, section ("__DATA,__objc_const"))) = {    0, __OFFSETOFIVAR__(struct Sark, _name), sizeof(struct Sark_IMPL),     (unsigned int)0,     0,     "Sark",    (const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_Sark,    0,     (const struct _ivar_list_t *)&_OBJC_$_INSTANCE_VARIABLES_Sark,    0,     (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Sark,};

解惑

1)为什么能够正常运行,并调用到speak方法?

id cls = [Sark class];void *obj = &cls;[(__bridge id)obj speak];

obj被转换成了一个指向Sark Class的指针,然后使用id转换成了objc_object类型。这个时候的obj已经相当于一个Sark的实例对象(但是和使用[Sark new]生成的对象还是不一样的),我们回想下中objc_object结构体的构成就是一个指向Class的isa指针。

这个时候我们再回想下中objc_msgSend的工作流程,在代码中的obj指向的Sark Class中能够找到speak方法,所以代码能够正常运行。

2) 为什么self.name的输出为 <Test: 0x1001002d0> ?

我们在测试代码中加入一些调试代码和Log如下:

- (void)speak{     unsigned int numberOfIvars = 0;    Ivar *ivars = class_copyIvarList([self class], &numberOfIvars);    for(const Ivar *p = ivars; p < ivars+numberOfIvars; p++) {        Ivar const ivar = *p;        ptrdiff_t offset = ivar_getOffset(ivar);        const char *name = ivar_getName(ivar);        NSLog(@"Sark ivar name = %s, offset = %td", name, offset);    }    NSLog(@"my name is %p", &_name);    NSLog(@"my name is %@", *(&_name));}@implementation Test- (instancetype)init{    self = [super init];    if (self) {        NSLog(@"Test instance = %@", self);        void *self2 = (__bridge void *)self;        NSLog(@"Test instance pointer = %p", &self2);        id cls = [Sark class];        NSLog(@"Class instance address = %p", cls);        void *obj = &cls;        NSLog(@"Void *obj = %@", obj);        [(__bridge id)obj speak];    }    return self;}@end

输出结果如下:

2014-11-11 00:56:02.464 Test[10475:1071029] Test instance = 
2014-11-11 00:56:02.464 Test[10475:1071029] Test instance pointer = 0x7fff5fbff7c82014-11-11 00:56:02.465 Test[10475:1071029] Class instance address = 0x1000023c82014-11-11 00:56:02.465 Test[10475:1071029] Void *obj =
2014-11-11 00:56:02.465 Test[10475:1071029] Sark ivar name = _name, offset = 82014-11-11 00:56:02.465 Test[10475:1071029] my name is 0x7fff5fbff7c82014-11-11 00:56:02.465 Test[10475:1071029] my name is

Sark中Propertyname最终被转换成了Ivar加入到了类的结构中,Runtime通过计算成员变量的地址偏移来寻找最终Ivar的地址,我们通过上述输出结果,可以看到 Sark的对象指针地址加上Ivar的偏移量之后刚好指向的是Test对象指针地址。

这里的原因主要是因为在C中,局部变量是存储到内存的栈区,程序运行时栈的生长规律是从地址高到地址低。C语言到头来讲是一个顺序运行的语言,随着程序运行,栈中的地址依次往下走。

看下图,可以清楚的展示整个计算的过程:

我们可以做一个另外的实验,把Test Class 的init方法改为如下代码:

@interface Father : NSObject@end@implementation Father@end@implementation Test- (instancetype)init{    self = [super init];    if (self) {        NSLog(@"Test instance = %@", self);        id fatherCls = [Father class];        void *father;        father = (void *)&fatherCls;        id cls = [Sark class];        void *obj;        obj = (void *)&cls;        [(__bridge id)obj speak];    }    return self;}@end

你会发现这个时候的输出变成了:

2014-11-08 21:40:36.724 Test[4845:543231] Test instance = 
2014-11-08 21:40:36.725 Test[4845:543231] ivar name = _name, offset = 82014-11-08 21:40:36.726 Test[4845:543231] Sark instance = 0x7fff5fbff7b82014-11-08 21:40:36.726 Test[4845:543231] my name is 0x7fff5fbff7c02014-11-08 21:40:36.726 Test[4845:543231] my name is

关于C语言内存分配和使用的问题可参考这篇文章 。


  • 本文是关于Objective C Runtime的学习笔记。有不对的地方,欢迎大家指正。
  • 感谢和分享题目。
  • 本文由发表于 .
  • 版权声明:自由转载-非商用-非衍生-保持署名 |

转载于:https://www.cnblogs.com/xuejinhui/p/4262308.html

你可能感兴趣的文章
在16aspx.com上下了一个简单商品房销售系统源码,怎么修改它的默认登录名和密码...
查看>>
c++回调函数
查看>>
linux下Rtree的安装
查看>>
【Java】 剑指offer(53-2) 0到n-1中缺失的数字
查看>>
Delphi中ListView类的用法
查看>>
多米诺骨牌
查看>>
Linq 学习(1) Group & Join--网摘
查看>>
asp.net 调用前台JS调用后台,后台掉前台JS
查看>>
Attribute(特性)与AOP
查看>>
苹果手表:大方向和谷歌一样,硬件分道扬镳
查看>>
Competing Consumers Pattern (竞争消费者模式)
查看>>
Android面试收集录15 Android Bitmap压缩策略
查看>>
PHP魔术方法之__call与__callStatic方法
查看>>
ubuntu 安装后的配置
查看>>
web前端之路,js的一些好书(摘自聂微东 )
查看>>
【模板】对拍程序
查看>>
【转】redo与undo
查看>>
解决升级系统导致的 curl: (48) An unknown option was passed in to libcurl
查看>>
Java Session 介绍;
查看>>
spoj TBATTLE 质因数分解+二分
查看>>