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. 关于原生JS获取class,ID等属性的一些封装

    一.传统上获取是通过document.getElementById获取元素的ID属性,通过总结与学习总结一下获取元素class以及id属性的一些封装; 1.创建构造函数,这里面不需要多解释什么:(主要 ...

  2. Android 支付宝H5 没有回调

    今天测试反馈问题,说,手机上没有安装支付宝的,调用支付宝支付之后,没有回调.不提示成功也不提示失败. 我自己试了半天也都是没有问题 .后来终于可以试出来了. 发现原来是,清单里面注册的Activity ...

  3. activity堆栈式管理

    package com.chinaCEB.cebActivity.utils; import java.util.Stack; import android.app.Activity; import ...

  4. Web 开发的未来:React、Falcor 和 ES6

    Web 开发的未来:React.Falcor 和 ES6 Widen是一家数字资产管理解决方案提供商.目前,其技术栈还非常传统,包括服务器端的Java.浏览器端的AngularJS.提供REST AP ...

  5. 初学JS——实现基于计时器的小游戏。

    这几天一直在看网易云课堂上免费的JS课程,正好今天看到讲了计时器setInterval,第一感觉就是像C#里的TIMER.然后课程里举得例子正好通过计时器改变新生成窗口的位置, 然后就突然有了灵感!可 ...

  6. 【活动】参加葡萄城控件主办的“谁是报表达人”知识评测活动,赢取iPad Mini2

    一.参与资格 从事报表开发的博客园用户 二.活动时间 4月1日-4月30日 三. 活动形式 在活动期间,活动参与者只要回答从题库中随机抽出的与报表相关的六道题,就可以知道自己的报表知识等级.同时活动主 ...

  7. python-configparser模块,xml.etree模块

    操作键值对文件 #文件db格式为 [section] a = 1 b = 2 [section1] d = 3 c = 4 import configparser #获取所有节点 config = c ...

  8. CSU-1170 A Simple Problem

    题目链接 http://acm.csu.edu.cn:20080/csuoj/problemset/problem?pid=1170 题目 Description ​ 在一个由N个整数组成的数列中,最 ...

  9. Thread suspend()挂起resume()恢复

    import javax.swing.*;import java.awt.*;import java.awt.event.ActionEvent;import java.awt.event.Actio ...

  10. xpath属性值的模糊匹配

    得至:http://bbs.csdn.net/topics/390857942  最后一楼 //div[contains(@class,'Number Skill')]