`
啸笑天
  • 浏览: 3465998 次
  • 性别: Icon_minigender_1
  • 来自: China
社区版块
存档分类
最新评论

Objective-C 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

 

 

thx:

http://chun.tips/blog/2014/11/08/bao-gen-wen-di-objective%5Bnil%5Dc-runtime(4)%5Bnil%5D-cheng-yuan-bian-liang-yu-shu-xing/

http://blog.sunnyxx.com/

 

 

 

 

分享到:
评论

相关推荐

    Objective-C Runtime Guide.pdf

    Runtime环境允许程序员在运行时动态地获取类的信息、方法列表以及实例变量等,这些特性为Objective-C提供了高度的灵活性。 #### 二、Runtime环境下的类与对象操作 1. **获取类信息** - 使用`objc_getClass`函数...

    Objective-C Runtime Programming Guide

    - Objective-C中的属性提供了一种声明式的接口,用于访问实例变量。 - 属性可以定义为只读或可读写,并且可以指定访问器方法的名称。 **12. 属性类型与函数:** - 属性类型字符串用来描述属性的类型。 - 属性的...

    iOS Objective-C Runtime v723 & Runloop 打包源码

    Objective-C Runtime是Objective-C语言的核心,它负责在运行时管理类、对象、方法等。在objc4-723源码中,你可以看到以下关键组件: 1. 类与对象:Runtime提供了创建和管理类的机制。例如,`objc_class`结构体表示...

    Objective-C run time

    属性是Objective-C语言中用于声明实例变量的便捷方式。文档提供了属性类型和相关功能的详细描述,包括属性类型字符串和属性属性描述的例子。 了解Objective-C运行时系统的工作机制以及如何利用它,对想要深入掌握...

    Objective-C 2.0 Runtime Programming Guide

    《Objective-C 2.0 Runtime Programming Guide》是Apple公司为开发者提供的一份详尽的技术文档,专注于Objective-C 2.0运行时编程的深入解析。这份文档不仅对Objective-C语言的核心概念进行了阐述,还深入探讨了运行...

    Manning.Objective-C.Fundamentals.Sep.2011.rar

    - **变量与数据类型**:Objective-C支持C语言的基本数据类型,同时有特有的对象类型。 - **关键字与语句**:如`@interface`, `@implementation`, `@protocol`, `@class`, `@property`等。 - **方法定义**:...

    Effective Objective-C 2.0&Obj;-C高级编程

    - Block是Objective-C中的闭包,可以捕获和存储上下文中的变量,常用于异步编程和回调函数。 5. **KVC(Key-Value Coding)和KVO(Key-Value Observing)**: - KVC提供了一种间接访问对象属性的方式,无需直接...

    iOS开发Objective-C项目工程混淆脚本.zip

    3. **运行时混淆**:在运行时动态生成代码或者改变方法实现,这在Objective-C中尤为方便,因为Objective-C支持消息传递和Runtime机制。 4. **混淆类别(Category)**:利用Category在运行时添加方法的特性,可以将...

    Objective-C基础教程(第2版)-有目录

    11. **Objective-C Runtime**:了解Objective-C运行时系统的工作原理,包括方法解析、类加载等。 12. **Cocoa与Cocoa Touch框架**:介绍Apple提供的主要开发框架,学习如何使用UIKit、Foundation等库来创建应用。 ...

    Objective-C开发语言

    5. **属性(Property)**:属性提供了一种简洁的方法来声明类的成员变量,并且可以控制对这些变量的访问方式。 6. **块(Block)**:块是Objective-C中的匿名函数,可以用来表示回调函数或执行一次性任务。 #### 五、...

    Learn Objective-C On The Mac.rar

    Objective-C是Apple的C语言家族的重要成员,是开发Apple生态系统应用程序的基础。这本书深入浅出地介绍了Objective-C语言的核心概念,以及如何在Mac环境下进行有效的编程。 Objective-C是一种面向对象的语言,它在...

    Objective-C 运行环境原理

    Objective-C 是一种面向对象的编程语言,它结合了C语言的基础语法与Smalltalk的面向对象能力。Objective-C 的一大特色是其强大的运行时特性,这些特性使得它能够在程序执行期间动态地处理很多任务,比如消息传递、...

    The Objective-C Programming Language

    7. **运行时(Runtime):**Objective-C的运行时环境提供了对类、方法、属性等的动态操作。 #### 四、Objective-C的应用框架 - **Cocoa:**Cocoa是苹果为Mac OS X提供的面向对象的应用程序框架集合,它基于Objective...

    Pro Objective-C

    根据提供的文件信息,我们可以归纳出一系列与Objective-C相关的知识点,主要围绕书中的章节内容展开。以下是对这些知识点的详细解析: ### 1. Getting Started 本章为读者提供了Objective-C编程语言的基础入门介绍...

    Objective-C.2.0程序设计_原书第2版

    《Objective-C.2.0程序设计_原书第2版》是面向Objective-C语言学习者的一本权威指南,深入解析了Objective-C.2.0版本的特性与编程技巧,为读者提供了一条从入门到精通的清晰路径。本书不仅适合初学者作为入门教材,...

    The Objective-C 2.0 Programming Language

    属性(Properties)是Objective-C 2.0引入的特性,它简化了访问实例变量的代码。通过使用属性,开发者可以轻松地生成存取方法(getter和setter)。这意味着开发者无需手动编写这些方法,而是通过简单的属性声明,...

    The Objective-C 2.0 Programming.PDF

    10. **Objective-C runtime**:Objective-C的运行时系统是其动态性的基础,允许在运行时检查类信息,动态修改类和对象,以及执行其他高级操作。 学习Objective-C 2.0不仅意味着掌握一种编程语言,还意味着理解苹果...

    Objective-C 基础教程

    - **Core Foundation(核心基础框架)**:介绍了Core Foundation框架与Objective-C属性之间的交互。 - **Subclassing with Properties(带有属性的子类化)**:讨论了如何在子类中使用属性。 #### 七、类别与扩展 ...

    千锋3G学院-IPHONE_iOS系列课程之Objective-C【分辨率1024*768】

    Objective-C起源于C语言,它扩展了C的语法,引入了Smalltalk式的消息传递机制,使得面向对象编程更为灵活。在iOS开发中,Objective-C是Foundation框架和AppKit框架(macOS)或UIKit框架(iOS)的基础,提供了大量...

Global site tag (gtag.js) - Google Analytics