iOS runtime探究(一): 从runtime开始理解面向对象的类到面向过程的结构体

你要知道的runtime都在这里

转载请注明出处 http://blog.csdn.net/u014205968/article/details/67639205

本文主要讲解runtime相关知识,从原理到实践,由于包含内容过多分为以下五篇文章详细讲解,可自行选择需要了解的方向:

本文是系列文章的第一篇文章从runtime开始: 理解面向对象的类到面向过程的结构体,主要从runtime出发讲解面向对象的类是如何转变为面向过程的结构体,来探究OC对类的处理本质。

什么是runtime

runtime就是运行时,在实际开发中使用runtime的场景并不多,但是了解runtime有助于我们更好的理解OC的原理,从而提高开发水平。

runtime很强大,是OC最重要的一部分也是OC最大的特色,可以不夸张的说runtime成就了OC,尽管runtime是OC的一个模块而已。

我们都知道高级编程语言想要成为可执行文件需要先编译为汇编语言再汇编为机器语言,机器语言也是计算机能够识别的唯一语言,但是OC并不能直接编译为汇编语言,而是要先转写为纯C语言再进行编译和汇编的操作,从OC到C语言的过渡就是由runtime来实现的。然而我们使用OC进行面向对象开发,而C语言更多的是面向过程开发,这就需要将面向对象的类转变为面向过程的结构体,本文正是通过runtime源码分析来讲解runtime是如何将面向对象的类转变为面向过程的结构体。

深入代码理解instance、class object、metaclass

面向对象编程中,最重要的概念就是类,下面我们就从代码入手,看看OC是如何实现类的。

前文一直在说runtime将面向对象的类转变为面向过程的结构体,那这个结构体到底是什么样子的?打开#import<objc/objc.h>文件,可以发现以下几行代码

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

通过注释和代码不难发现,我们创建的一个对象或实例其实就是一个struct objc_object结构体,而我们常用的id也就是这个结构体的指针。有如下代码:

//以下两种写法都成立
id str = [[NSString alloc] init];
NSString *str = [[NSString alloc] init];

通过上述代码可以看出,我们创建的NSString类的实例str其实就是一个struct objc_object结构体指针,所以不管是Foundation框架中的类或是自定义的类,我们创建的类的实例最终获取的都是一个结构体指针,这个结构体只有一个成员变量就是Class类型的isa指针,Class是结构体指针,指向struct objc_class,那这个结构体又是什么呢?这里先透露一句话str is a NSString,再加上Class这个指针的名字,我们不难猜测,Class就是代表NSString这个类。

接下来会详细讲解这个结构体,现在再看另一个例子,有时我们也会通过下述方法来创建一个实例:

NSString *str = [[NSString alloc] initWithString: @"Hello World"];
Class c = [str class];
NSString *str2 = [[c alloc] initWithString: @"Hello World"];

可能你已经发现了,通过实例对象调用的class方法,我们能够获取到一个Class类型的变量,我们可以通过这个Class来创建相应的实例对象。

实际上,OC中的类也是一个对象,称为类对象,上述方法中通过[str class]方法获取到的就是NSString类类对象,接着我们就可以通过这个类对象来创建实例对象,那这个类对象又是什么东西呢?打开#import<objc/runtime.h>文件,我们可以找到结构体struct objc_class的定义,该结构体定义如下:

文件objc/runtime.h中有如下定义:
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

    Class super_class
    const char *name
    long version
    long info
    long instance_size
    struct objc_ivar_list *ivars
    struct objc_method_list **methodLists
    struct objc_cache *cache
    struct objc_protocol_list *protocols
}
/* Use `Class` instead of `struct objc_class *` */

文件objc/objc.h文件中有如下定义
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

struct objc_class结构体定义了很多变量,通过命名不难发现,结构体里保存了指向父类的指针、类的名字、版本、实例大小、实例变量列表、方法列表、缓存、遵守的协议列表等,一个类包含的信息也不就正是这些吗?没错,类对象就是一个结构体struct objc_class,这个结构体存放的数据称为元数据(metadata),该结构体的第一个成员变量也是isa指针,这就说明了Class本身其实也是一个对象,因此我们称之为类对象类对象在编译期产生用于创建实例对象,是单例。

类对象中的元数据存储的都是如何创建一个实例的相关信息,那么类对象类方法应该从哪里创建呢?就是从isa指针指向的结构体创建,类对象isa指针指向的我们称之为元类(metaclass)元类中保存了创建类对象以及类方法所需的所有信息,因此整个结构应该如下图所示:

通过上图我们可以清晰的看出来一个实例对象也就是struct objc_object结构体它的isa指针指向类对象类对象isa指针指向了元类,super_class指针指向了父类的类对象,而元类super_class指针指向了父类的元类,那元类isa指针又指向了什么?为了更清晰的表达直接使用一个大神画的图。

通过上图我们可以看出整个体系构成了一个自闭环,如果是从NSObject中继承而来的上图中的Root class就是NSObject。至此,整个实例类对象元类的概念也就讲清了,接下来我们在代码中看看这些概念该怎么应用。

@interface Person : NSObject

@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSUInteger age;

@end

@implementation Person

@synthesize name = _name;
@synthesize age = _age;

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        Class c1 = [p class];
        Class c2 = [Person class];
        //输出 1
        NSLog(@"%d", c1 == c2);
    }
    return 0;
}

c1是通过一个实例对象获取的Class,实例对象可以获取到其类对象,类名作为消息的接受者时代表的是类对象,因此类对象获取Class得到的是其本身,同时也印证了类对象是一个单例的想法。

那么如果我们想获取isa指针的指向对象呢?

介绍两个函数

OBJC_EXPORT BOOL class_isMetaClass(Class cls)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

OBJC_EXPORT Class object_getClass(id obj)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

class_isMetaClass用于判断Class对象是否为元类object_getClass用于获取对象的isa指针指向的对象。

再看如下代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        //输出1
        NSLog(@"%d", [p class] == object_getClass(p));
        //输出0
        NSLog(@"%d", class_isMetaClass(object_getClass(p)));
        //输出1
        NSLog(@"%d", class_isMetaClass(object_getClass([Person class])));
        //输出0
        NSLog(@"%d", object_getClass(p) == object_getClass([Person class]));
    }
    return 0;
}

通过代码可以看出,一个实例对象通过class方法获取的Class就是它的isa指针指向的类对象,而类对象不是元类类对象isa指针指向的对象是元类

总结

通过上文的代码分析,我们已经了解了OC中的类和实例是如何映射到C语言结构体的,实例对象是一个结构体,这个结构体只有一个成员变量,指向构造它的那个类对象,这个类对象中存储了一切实例对象需要的信息包括实例变量、实例方法等,而类对象是通过元类创建的,元类中保存了类变量和类方法,这样就完美解释了整个类和实例是如何映射到结构体的。

下一步

了解类到结构体映射只是揭开runtime神秘面纱的第一步,下一篇博客将会介绍OC的消息传递机制以及runtime对OC消息传递所做的具体操作,感兴趣的读者可以继续学习下一篇文章从runtime开始: 深入理解OC消息转发机制

备注

由于作者水平有限,难免出现纰漏,如有问题还请不吝赐教。

时间: 2024-10-12 07:23:29

iOS runtime探究(一): 从runtime开始理解面向对象的类到面向过程的结构体的相关文章

iOS runtime探究(三): 从runtime开始理解OC的属性property

你要知道的runtime都在这里 转载请注明出处 http://blog.csdn.net/u014205968/article/details/67639303 本文主要讲解runtime相关知识,从原理到实践,由于包含内容过多分为以下五篇文章详细讲解,可自行选择需要了解的方向: 从runtime开始: 理解面向对象的类到面向过程的结构体 从runtime开始: 深入理解OC消息转发机制 从runtime开始: 理解OC的属性property 从runtime开始: 实践Category添加属

iOS runtime探究(二): 从runtime開始深入理解OC消息转发机制

你要知道的runtime都在这里 转载请注明出处 http://blog.csdn.net/u014205968/article/details/67639289 本文主要解说runtime相关知识,从原理到实践.由于包括内容过多分为下面五篇文章详细解说.可自行选择须要了解的方向: 从runtime開始: 理解面向对象的类到面向过程的结构体 从runtime開始: 深入理解OC消息转发机制 从runtime開始: 理解OC的属性property 从runtime開始: 实践Category加入属

iOS runtime探究(五): 从runtime开始深入weak实现机理

你要知道的runtime都在这里 转载请注明出处 http://blog.csdn.net/u014205968/article/details/67639341 本文主要讲解runtime相关知识,从原理到实践,由于包含内容过多分为以下五篇文章详细讲解,可自行选择需要了解的方向: 从runtime开始: 理解面向对象的类到面向过程的结构体 从runtime开始: 深入理解OC消息转发机制 从runtime开始: 理解OC的属性property 从runtime开始: 实践Category添加属

iOS runtime探究(四): 从runtiem开始实践Category添加属性与黑魔法method swizzling

你要知道的runtime都在这里 转载请注明出处 http://blog.csdn.net/u014205968/article/details/67639335 本文主要讲解runtime相关知识,从原理到实践,由于包含内容过多分为以下五篇文章详细讲解,可自行选择需要了解的方向: 从runtime开始: 理解面向对象的类到面向过程的结构体 从runtime开始: 深入理解OC消息转发机制 从runtime开始: 理解OC的属性property 从runtime开始: 实践Category添加属

IOS开发语言Swift入门连载---类和结构体

IOS开发语言Swift入门连载-类和结构体 类和结构体是人们构建代码所用的一种通用且灵活的构造体.为了在类和结构体中实现各种功能,我们必须要严格按照常量.变量以及函数所规定的语法规则来定义属性和添加方法. 与其他编程语言所不同的是,Swift 并不要求你为自定义类和结构去创建独立的接口和实现文件.你所要做的是在一个单一文件中定义一个类或者结构体,系统将会自动生成面向其它代码的外部接口. 注意: 通常一个类 的实例被称为对象 .然而在Swift 中,类和结构体的关系要比在其他语言中更加的密切,本

iOS 之动态运行时 runtime

前言: 最近研究runtime,觉得里面的东西好深,所以决定先把苹果提供的runtime.h文件简单的翻译一下,关于用法会在之后进行一些分享. /* Types */ #if !OBJC_TYPES_DEFINED /// An opaque type that represents a method in a class definition. typedef struct objc_method *Method; /// An opaque type that represents an i

IOS开发中 RunLoop,RunTime

1.Objective-C中的函数调用 对于C语言,函数调用是由编译器直接转化完成的,在编译时程序就开始查找要执行的函数(C语言函数调用原理).而在OC中,我们将函数调用称为消息发送.在编译时程序不查找要执行的函数,必须等到真正运行时,程序才查找要执行的函数. 例子:在C语言中,仅申明一个函数,不去实现.其他地方调用此函数.编译时就会报错(C语言编译时查找要执行的函数,找不到所以报错).而同样的情况在OC中并不会报错,只有在运行时候才会报错.(OC运行时才查找要执行的函数) 2.Objectiv

Runtime的初步认识——结构体与类

Runtime的初步认识 Runtime的初步认识 Runtime介绍 类与结构体的关系 结构体解析 结构体的作用 Runtime介绍 学习一个东西至少要先知道它是个啥,你一定听说过"运行时是 Objective-C 的一个特色",这里的"运行时"就是指 runtime 了. runtime是在自 iOS 平台开放并基于 Objective-C 语言开发后的一个编程语言上的高级技术. 学习runtime的目的并不是为了开发,而是让你更好的理解 Objective-C

ios中关于对锚点的理解

锚点在ios中见到的地方不多,大部分用在动画中. 今天看到一个动画,上面都是关于锚点的,锚点这个概念在两年前看cocos2d得基本概念时接触过,当时没怎么看,今天看到了,就在好好的学一下. 看了一篇blog,是关于锚点的,就借鉴一些上面的图像: cocos2d里采用OpenGL ES坐标系,坐标原点在屏幕左下角.而ios采用的是Quartz 2D坐标系,坐标原点在屏幕左上角. 在cocos2d和ios中分别把视图的坐标点设为(10,10),结果如下: 那么什么是锚点呢?下面以一个例子来说明: 比