http://chun.tips/blog/2014/11/08/bao-gen-wen-di-objective[nil]c-runtime(4)[nil]-cheng-yuan-bian-liang-yu-shu-xing/

上一篇笔记讲述了objc runtime中消息和Category的细节,本篇笔记主要是讲述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;
} @end int 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 <Test: 0x1001002d0>

为什么呢?

前几节博文中多次讲到了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];
}
@end int 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];
}
@end int 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 = 8
2014-11-08 18:24:38.893 Test[4143:466864] current pointer = 0x1001002d0
2014-11-08 18:24:38.893 Test[4143:466864] age pointer = 0x1001002d8
2014-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 Sark static 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]生成的对象还是不一样的),我们回想下Runtime的第二篇博文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 = <Test: 0x10010fb60>
2014-11-11 00:56:02.464 Test[10475:1071029] Test instance pointer = 0x7fff5fbff7c8
2014-11-11 00:56:02.465 Test[10475:1071029] Class instance address = 0x1000023c8
2014-11-11 00:56:02.465 Test[10475:1071029] Void *obj = <Sark: 0x7fff5fbff7c0>
2014-11-11 00:56:02.465 Test[10475:1071029] Sark ivar name = _name, offset = 8
2014-11-11 00:56:02.465 Test[10475:1071029] my name is 0x7fff5fbff7c8
2014-11-11 00:56:02.465 Test[10475:1071029] my name is <Test: 0x10010fb60>

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 = <Test: 0x10010fb60>
2014-11-08 21:40:36.725 Test[4845:543231] ivar name = _name, offset = 8
2014-11-08 21:40:36.726 Test[4845:543231] Sark instance = 0x7fff5fbff7b8
2014-11-08 21:40:36.726 Test[4845:543231] my name is 0x7fff5fbff7c0
2014-11-08 21:40:36.726 Test[4845:543231] my name is <Father: 0x7fff5fbff7c8>

关于C语言内存分配和使用的问题可参考这篇文章 http://www.th7.cn/Program/c/201212/114923.shtml


刨根问底Objective-C Runtime(4)- 成员变量与属性的更多相关文章

  1. Objective-C Runtime 运行时之二:成员变量与属性(转载)

    在前面一篇文章中,我们介绍了Runtime中与类和对象相关的内容,从这章开始,我们将讨论类实现细节相关的内容,主要包括类中成员变量,属性,方法,协议与分类的实现. 本章的主要内容将聚集在Runtime ...

  2. Runtime之成员变量&属性&关联对象

    上篇介绍了Runtime类和对象的相关知识点,在4.5和4.6小节,也介绍了成员变量和属性的一些方法应用.本篇将讨论实现细节的相关内容. 在讨论之前,我们先来介绍一个很冷僻但又很有用的一个关键字:@e ...

  3. runtime第二部分成员变量和属性

    接上一篇 http://www.cnblogs.com/ddavidXu/p/5912306.html 转载来源http://www.jianshu.com/p/6b905584f536 http:/ ...

  4. Objective-C Runtime 运行时之二:成员变量与属性

    类型编码(Type Encoding) 作为对Runtime的补充,编译器将每个方法的返回值和参数类型编码为一个字符串,并将其与方法的selector关联在一起.这种编码方案在其它情况下也是非常有用的 ...

  5. runtime-对成员变量和属性的操作

    成员变量 首先我们来看看成员变量在runtime中是什么样的 在runtime中成员变量是一个objc_ivar类型的结构体,结构体定义如下 struct objc_ivar { char *ivar ...

  6. OC基础成员变量、属性变量、实例方法与类方法

    首先,为什么我们要定义一个新类呢?按照我的理解,就是为了抽象出来一个新的东西(也就是类),用来存储更多的数据变量和方法,一切类都直接或间接继承与NSObject. 在类的头文件里我们可以定义成员变量. ...

  7. C# 成员变量和属性的区别

    之前一直在C#中使用这两者, 却一直不知道成员变量和属性还是不一样的两种概念. 不过说不一样, 也不是完全对. 简单举个例子: public class myclass { public string ...

  8. 【iOS 开发】Objective - C 面向对象 - 方法 | 成员变量 | 隐藏封装 | KVC | KVO | 初始化 | 多态

    一. Objective-C 方法详解 1. 方法属性 (1) OC 方法传参机制 Object-C 方法传参机制 : OC 中得参数传递都是值传递, 传入参数的是参数的副本; -- 基本类型 (值传 ...

  9. iOS开发--成员变量与属性

    属性变量 @interface MyClass:NSObject{ MyObjecct *_object; } @property(nonamtic, retain) MyObjecct *objec ...

随机推荐

  1. 012---Django的用户认证组件

    知识预览 用户认证 回到顶部 用户认证 auth模块 ? 1 from django.contrib import auth django.contrib.auth中提供了许多方法,这里主要介绍其中的 ...

  2. Linux YUM (Yellowdog Updater, Modified) Commands for Package Management

    Linux YUM (Yellowdog Updater, Modified) Commands for Package Management In this article, we will lea ...

  3. Eclipse字体修改

    第一步: 第二步: 第三步: 第四步: 第五步: 第六步:

  4. Hadoop环境搭建 (伪分布式搭建)

    一,Hadoop版本下载 建议下载:Hadoop2.5.0 (虽然是老版本,但是在企业级别中运用非常稳定,新版本虽然添加了些小功能但是版本稳定性有带与考核) 1.下载地址: hadoop.apache ...

  5. zeppelin的数据集的优化

    前面我们介绍了zeppelin的修改,前面由于自己的原因,对zeppelin的修改过于多,现在由于优化了,我们两个类, 一个是zeppelin-server的NotebookServer的类的broa ...

  6. Kotlin 二分法算法游戏--猜价格

    本人最新想学习算法,算法是提高程序性能的关键! 程序就是数据结构和算法! 写了一个二分法的游戏,供大家参考: 当然,语言基于kotlin import java.util.* /** * Create ...

  7. Android 快捷方式的创建与查询 快捷方式问题大全 获取快捷方式在Launcher数据库中的信息 Failed to find provider info for com.android.la

    /** * 创建添加快捷方式 * 其中需要设置的有: * 1. 快捷方式的标题 * 2. 快捷方式的图标 * 3. 点击快捷方式后的跳转 */ public static void createSho ...

  8. Android stadio 自定义debug release keystore

    1.添加siggnig name 随意,不过按我写的就可以了.设置完成之后,你的build.grade就会多出来一些: android { signingConfigs { signingConfig ...

  9. 浅谈 css 之 position用法

    在 css中, position 属性有四个值可用: static(默认值).absolute.relative.fixed. relative:相对定位(相对于自身进行在常规流中的位置进行定位,保留 ...

  10. vue 搜索匹配

    computed: { broSeachData: function() { var browesData = this.browesData, searchVal = this.searchVal; ...