(转)Objective-C Runtime(3、4)-消息和Category 成员变量与属性

习题内容
下面的代码会?Compile Error / Runtime Crash / NSLog…?


@interface NSObject (Sark)
+ (void)foo;
@end
@implementation NSObject (Sark)
- (void)foo
{
    NSLog(@"IMP: -[NSObject(Sark) foo]");
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [NSObject foo];
        [[NSObject new] foo];
    }
    return 0;
}

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

2014-11-06 13:11:46.694 Test[14872:1110786] IMP: -[NSObject(Sark) foo]
2014-11-06 13:11:46.695 Test[14872:1110786] IMP: -[NSObject(Sark) foo]

使用clang -rewrite-objc main.m重写,我们可以发现 main 函数中两个方法调用被转换成如下代码:

 ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("foo"));
 ((void (*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new")), sel_registerName("foo"));

我们发现上述两个方法最终转换成使用 objc_msgSend 函数传递消息。
这里先看几个概念
objc_msgSend函数定义如下:

id objc_msgSend(id self, SEL op, ...)

关于 id 的解释请看objc runtime系列第二篇博文: objc runtime中Object & Class & Meta Class的细节
什么是 SEL
打开objc.h文件,看下SEL的定义如下:
`

typedef struct objc_selector *SEL;`

SEL是一个指向objc_selector结构体的指针。而 objc_selector 的定义并没有在runtime.h中给出定义。我们可以尝试运行如下代码:

SEL sel = @selector(foo);
NSLog(@"%s", (char *)sel);
NSLog(@"%p", sel);
const char *selName = [@"foo" UTF8String];
SEL sel2 = sel_registerName(selName);
NSLog(@"%s", (char *)sel2);
NSLog(@"%p", sel2);

输出如下:

2014-11-06 13:46:08.058 Test[15053:1132268] foo
2014-11-06 13:46:08.058 Test[15053:1132268] 0x7fff8fde5114
2014-11-06 13:46:08.058 Test[15053:1132268] foo
2014-11-06 13:46:08.058 Test[15053:1132268] 0x7fff8fde5114

Objective-C在编译时,会根据方法的名字生成一个用来区分这个方法的唯一的一个ID。只要方法名称相同,那么它们的ID就是相同的。
两个类之间,不管它们是父类与子类的关系,还是之间没有这种关系,只要方法名相同,那么它的SEL就是一样的。每一个方法都对应着一个SEL。编译器会根据每个方法的方法名为那个方法生成唯一的SEL。这些SEL组成了一个Set集合,当我们在这个集合中查找某个方法时,只需要去找这个方法对应的SEL即可。而SEL本质是一个字符串,所以直接比较它们的地址即可。
当然,不同的类可以拥有相同的selector。不同类的实例对象执行相同的selector时,会在各自的方法列表中去根据selector去寻找自己对应的IMP。
那么什么是IMP呢
继续看定义:
typedef id (*IMP)(id, SEL, ...);

IMP本质就是一个函数指针,这个被指向的函数包含一个接收消息的对象id,调用方法的SEL,以及一些方法参数,并返回一个id。因此我们可以通过SEL获得它所对应的IMP,在取得了函数指针之后,也就意味着我们取得了需要执行方法的代码入口,这样我们就可以像普通的C语言函数调用一样使用这个函数指针。
那么 objc_msgSend 到底是怎么工作的呢?
在Objective-C中,消息直到运行时才会绑定到方法的实现上。编译器会把代码中[target doSth]转换成 objc_msgSend消息函数,这个函数完成了动态绑定的所有事情。它的运行流程如下:
检查selector是否需要忽略。(ps: Mac开发中开启GC就会忽略retain,release方法。)

检查target是否为nil。如果为nil,直接cleanup,然后return。(这就是我们可以向nil发送消息的原因。)

然后在target的Class中根据Selector去找IMP

寻找IMP的过程:
先从当前class的cache方法列表(cache methodLists)里去找

找到了,跳到对应函数实现

没找到,就从class的方法列表(methodLists)里找

还找不到,就到super class的方法列表里找,直到找到基类(NSObject)为止

最后再找不到,就会进入动态方法解析和消息转发的机制。(这部分知识,下次再细谈)

那么什么是方法列表呢?
上一篇博文中提到了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;
struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;
    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}```

1) objc_method_list 就是用来存储当前类的方法链表,objc_method存储了类的某个方法的信息。
**Method**


`typedef struct objc_method *Method;`

Method 是用来代表类中某个方法的类型,它实际就指向objc_method结构体,如下:

struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;

method_types是个char指针,存储着方法的参数类型和返回值类型。
SEL 和 IMP 就是我们上文提到的,所以我们可以理解为objc_class中 method list保存了一组SEL<->IMP的映射。
2)objc_cache 用来缓存用过的方法,提高性能。
**Cache**

`typedef struct objc_cache *Cache                             OBJC2_UNAVAILABLE;`

实际指向objc_cache结构体,如下:

struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};

mask: 指定分配cache buckets的总数。在方法查找中,Runtime使用这个字段确定数组的索引位置

occupied: 实际占用cache buckets的总数

buckets: 指定Method数据结构指针的数组。这个数组可能包含不超过mask+1个元素。需要注意的是,指针可能是NULL,表示这个缓存bucket没有被占用,另外被占用的bucket可能是不连续的。这个数组可能会随着时间而增长。

objc_msgSend每调用一次方法后,就会把该方法缓存到cache列表中,下次的时候,就直接优先从cache列表中寻找,如果cache没有,才从methodLists中查找方法。
**说完了 objc_msgSend, 那么题目中的Category又是怎么工作的呢?**
**继续看概念**
我们知道Catagory可以动态地为已经存在的类添加新的方法。这样可以保证类的原始设计规模较小,功能增加时再逐步扩展。在runtime.h中查看定义:
`
typedef struct objc_category *Category;`

同样也是指向一个 objc_category 的C 结构体,定义如下:

struct objc_category {
char *category_name OBJC2_UNAVAILABLE;
char *class_name OBJC2_UNAVAILABLE;
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE;
struct objc_method_list *class_methods OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;

通过上面的结构体,大家可以很清楚的看出存储的内容。我们继续往下看,打开objc源代码,在 objc-runtime-new.h中我们可以发现如下定义:

struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
};

上面的定义需要提到的地方有三点:
name 是指 class_name 而不是 category_name

cls是要扩展的类对象,编译期间是不会定义的,而是在Runtime阶段通过name对应到对应的类对象

instanceProperties表示Category里所有的properties,这就是我们可以通过objc_setAssociatedObject和objc_getAssociatedObject增加实例变量的原因,不过这个和一般的实例变量是不一样的

为了验证上述内容,我们使用clang -rewrite-objc main.m重写,题目中的Category被编译器转换成了这样:

// @interface NSObject (Sark)
// + (void)foo;
/* @end */
// @implementation NSObject (Sark)
static void _I_NSObject_Sark_foo(NSObject * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_dd1ee3_mi_0);
}
// @end
static struct _category_t _OBJC__CATEGORY_NSObject__Sark attribute ((used, section ("__DATA,__objc_const"))) =
{
"NSObject",
0, // &OBJC_CLASS__NSObject,
(const struct _method_list_t *)&_OBJC__CATEGORY_INSTANCE_METHODS_NSObject__Sark,
0,
0,
0,
};
static struct category_t *L_OBJC_LABEL_CATEGORY [1] attribute((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC__CATEGORY_NSObject__Sark,
};

_OBJC__CATEGORY_NSObject__Sark是按规则生成的字符串,我们可以清楚的看到是NSObject类,且Sark是NSObject类的Category

_category_t结构体第二项 classref_t 没有数据,验证了我们上面的说法

由于题目中只有 - (void)foo方法,所以结构体中存储的list只有第三项instanceMethods被填充。

_I_NSObject_Sark_foo代表了Category的foo方法,I表示实例方法

最后这个类的Category生成了一个数组,存在了__objc_catlist里,目前数组的内容只有一个&_OBJC__CATEGORY_NSObject__Sark

**最终这些Category里面的方法是如何被加载的呢?**
1.打开objc源代码,找到 objc-os.mm, 函数_objc_init为runtime的加载入口,由libSystem调用,进行初始化操作。
2.之后调用objc-runtime-new.mm -> map_images加载map到内存
3.之后调用objc-runtime-new.mm->_read_images初始化内存中的map, 这个时候将会load所有的类,协议还有Category。NSOBject的+load方法就是这个时候调用的
这里贴上Category被加载的代码:

// Discover categories.
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
for (i = 0; i < count; i++) {
category_t cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category ???(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
BOOL classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
if (cat->classMethods || cat->protocols
/
|| cat->classProperties */)
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}

1) 循环调用了 _getObjc2CategoryList方法,这个方法的实现是:
1

GETSECT(_getObjc2CategoryList,        category_t *,    "__objc_catlist");

方法中最后一个参数__objc_catlist就是编译器刚刚生成的category数组
2) load完所有的categories之后,开始对Category进行处理。
从上面的代码中我们可以发现:实例方法被加入到了当前的类对象中, 类方法被加入到了当前类的Meta Class中 (cls->ISA)
Step 1. 调用addUnattachedCategoryForClass方法
Step 2. 调用remethodizeClass方法, 在remethodizeClass的实现里调用attachCategoryMethods

static void
attachCategoryMethods(Class cls, category_list *cats, bool flushCaches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
method_list_t **mlists = (method_list_t *)
_malloc_internal(cats->count * sizeof(
mlists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int i = cats->count;
BOOL fromBundle = NO;
while (i--) {
method_list_t *mlist = cat_method_list(cats->list[i].cat, isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= cats->list[i].fromBundle;
}
}
attachMethodLists(cls, mlists, mcount, NO, fromBundle, flushCaches);
_free_internal(mlists);
}

这里把一个类的category_list的所有方法取出来生成了method list。这里是倒序添加的,也就是说,新生成的category的方法会先于旧的category的方法插入。
之后调用attachMethodLists将所有方法前序添加进类的method list中,如果原来类的方法列表是a,b,Category的方法列表是c,d。那么插入之后的方法列表将会是c,d,a,b。
**小发现**
看上面被编译器转换的代码,我们发现Category头文件被注释掉了,结合上面category的加载过程。这就是我们即使没有import category的头文件,都能够成功调用到Category方法的原因。
runtime加载完成后,Category的原始信息在类结构中将不会存在。
**解惑**
根据上面提到的知识,我们对题目中的代码进行分析。
1) objc runtime加载完后,NSObject的Sark Category被加载。而NSObject的Sark Category的头文件 + (void)foo 并没有实质参与到工作中,只是给编译器进行静态检查,所有我们编译上述代码会出现警告,提示我们没有实现 + (void)foo 方法。而在代码编译中,它已经被注释掉了。
2) 实际被加入到Class的method list的方法是 - (void)foo,它是一个实例方法,所以加入到当前类对象NSObject的方法列表中,而不是NSObject Meta class的方法列表中。
3) 当执行 [NSObject foo]时,我们看下整个objc_msgSend的过程:
结合上一篇Meta Class的知识:
objc_msgSend 第一个参数是  “(id)objc_getClass("NSObject")”,获得NSObject Class的对象。

类方法在Meta Class的方法列表中找,我们在load Category方法时加入的是- (void)foo实例方法,所以并不在NSOBject Meta Class的方法列表中

继续往 super class中找,在上一篇博客中我们知道,NSObject Meta Class的super class是NSObject本身。所以,这个时候我们能够找到- (void)foo 这个方法。

所以正常输出结果

4) 当执行[[NSObject new] foo],我们看下整个objc_msgSend的过程:
[NSObject new]生成一个NSObject对象。

直接在该对象的类(NSObject)的方法列表里找。

能够找到,所以正常输出结果。

**刨根问底Objective-C Runtime(4)- 成员变量与属性**
本篇笔记主要是讲述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

**为什么呢?**
前几节博文中多次讲到了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 。看下图:
![1419386363580125.png](http://upload-images.jianshu.io/upload_images/1864395-ef3ced36ac3e1213.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
上图中,左侧的数据就是地址偏移字节,我们对 ivar 的访问就可以通过 对象地址 + ivar偏移字节的方法。但是这又引发一个问题,看下图:
![1419386376731373.png](http://upload-images.jianshu.io/upload_images/1864395-da77e9c2ba7585d5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
我们增加了父类的ivar,这个时候布局就出错了,我们就不得不重新编译子类来恢复兼容性。
而Objective-C Runtime中使用了Non Fragile ivars,看下图:
![1419386388780906.png](http://upload-images.jianshu.io/upload_images/1864395-fa05d2ebf66729a3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
使用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,并且自动添加了我们熟悉的Set和Get方法。
我们这个时候回头看一下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的输出为?**
我们在测试代码中加入一些调试代码和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 = 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 = 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

Sark中Propertyname最终被转换成了Ivar加入到了类的结构中,Runtime通过计算成员变量的地址偏移来寻找最终Ivar的地址,我们通过上述输出结果,可以看到 Sark的对象指针地址加上Ivar的偏移量之后刚好指向的是Test对象指针地址。
这里的原因主要是因为在C中,局部变量是存储到内存的栈区,程序运行时栈的生长规律是从地址高到地址低。C语言到头来讲是一个顺序运行的语言,随着程序运行,栈中的地址依次往下走。
看下图,可以清楚的展示整个计算的过程:
![1419386619962473.png](http://upload-images.jianshu.io/upload_images/1864395-3bbfa5fac4340b2f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
我们可以做一个另外的实验,把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 = 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,670评论 0 9
  • 转载:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麦子阅读 721评论 0 2
  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,161评论 0 7
  • 文中的实验代码我放在了这个项目中。 以下内容是我通过整理[这篇博客] (http://yulingtianxia....
    茗涙阅读 901评论 0 6
  • 本文详细整理了 Cocoa 的 Runtime 系统的知识,它使得 Objective-C 如虎添翼,具备了灵活的...
    lylaut阅读 788评论 0 4