您的位置:首页 > 移动开发 > Objective-C

Runtime学习笔记1--基础概念篇

2016-09-22 23:32 302 查看

前言

看了下ReactiveCocoa的源码发现RAC在给系统类添加rac_扩展的时候用到了很多Runtime层面的API,所以打算先从Runtime基础开始学起并纪录下来。由于网上关于Runtime的描述已经很详尽,所以就拾人牙慧,以作学习之用。

概念

Runtime是用C和汇编写的动态库,其将Objective-C跟C紧密结合起来,顾名思义,即“跑的时候“,其作用是:

让我们可以在系统在运行时创建、检查或者修改类、对象和方法等。这区别于我们平时在iOS开发的正常模式(即创建好一堆UI和一堆逻辑然后不管死活的点CMD+R),所以Runtime赋予了Objective-C很好的动态特性。

通过传递消息找出最终的代码。

Runtime 系列 1– 从一个崩溃谈起 作者如是说:

ObjC 是一种面向runtime(运行时)的语言,也就是说,它会尽可能地把代码执行的决策从编译和链接的时候,推迟到运行时。这给程序员写代码带来很大的灵活性,比如说你可以把消息转发给你想要的对象,或者随意交换一个方法的实现之类的。这就要求 runtime 能检测一个对象是否能对一个方法进行响应,然后再把这个方法分发到对应的对象去。我们拿 C 来跟 ObjC 对比一下。在 C 语言里面,一切从 main 函数开始,程序员写代码的时候是自上而下地,一个 C 的结构体或者说类吧,是不能把方法调用转发给其他对象的。但是在oc中,我们可以在运行时把上面的target换成其他对象,非常灵活。

可见其实Runtime就是一个项目最后的决策者,Runtime贯穿整个项目的生命周期。

从 objc_msgSend谈起

我们平时使用方法调用都是如此:

[target MethodName:var1];


其实在Runtime看来,这些都是伪代码。当项目开始编译时,这句代码会编译成如此:

objc_msgSend(target,@selector(MethodName:),var1);


objc_msgSend()是Runtime里面最基础也是最重要的函数。

objc_msgSend函数的原型如下:

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


上面的函数里面有两个参数id和SEL,我们分别看看。

id

一个指向类实例的指针:

typedef struct objc_object *id;


那objc_object又是啥呢:

struct objc_object { Class isa; };


objc_object结构体包含一个isa指针,根据isa指针就可以找到对象所属的类。

isa指针不总是指向实例对象所属的类,不能依靠它来确定类型,而是应该用class方法来确定实例对象的类。因为KVO的实现机理就是将被观察对象的isa指针指向一个中间类而不是真实的类,这是一种叫做 isa-swizzling 的技术。

SEL

selector在Objc中的表示类型(Swift中是Selector类)。selector是方法选择器,可以理解为区分方法的 ID,而这个 ID 的数据结构是SEL:

typedef struct objc_selector *SEL;


其实它就是个映射到方法的C字符串,你可以用 Objc 编译器命令@selector()或者 Runtime 系统的sel_registerName函数来获得一个SEL类型的方法选择器。

可以根据SEL(方法编号)去类方法列表找到对应的实例方法的实现,或者去元类方法列表找到对应的类方法的实现.

isa

所有的对象都是由其对应的类实例化而来。在Objective-C中,我们用到的几乎所有类都是NSObject类的子类。而NSObject的类定义格式如下:

@interface NSObject <NSObject> {
Class isa;
}


这个Class为何物?在objc.h中我们发现其仅仅是一个结构(struct)指针的typedef定义:

typedef struct objc_class *Class;


同样的,objcclass又是什么呢?在Objective-C2.0中,objcclass的定义如下:

struct objc_class {
Class isa;
}


我们知道isa指针指向的是该对象所属的类,对于实例对象的isa指针我们知道是指向其所属的类,但是实例对象所属的类的isa指针又指向谁呢?这里我们先记住一点:类本身也是对象!!

那么既然类本身也是对象,那么他所属的类是谁?答案就是:元类!!

所以实例对象所属的类的isa指针指向的是元类。

1. 类对象的实质

类对象是由编译器创建的,即在编译时所谓的类,就是指类对象(官方文档中是这样说的: The class object is the compiled version of the class)。

任何直接或间接继承了NSObject的类,它的实例对象(instance objec)中都有一个isa指针,指向它的类对象(class object)。这个类对象(class object)中存储了关于这个实例对象(instace object)所属的类的定义的一切:包括变量,方法,遵守的协议等等。

因此,类对象能访问所有关于这个类的信息,利用这些信息可以产生一个新的实例,但是类对象不能访问任何实例对象的内容。当你调用一个 “类方法” 例如 [NSObject alloc],你事实上是发送了一个消息给他的类对象。

2. 类对象和实例对象的区别

尽管类对象保留了一个类实例的原型,但它并不是实例本身。它没有自己的实例变量,也不能执行那些类的实例的方法(只有实例对象才可以执行实例方法)。然而,类的定义能包含那些特意为类对象准备的方法–类方法( 而不是的实例方法)。类对象从父类那里继承类方法,就像实例从父类那里继承实例方法一样。

类对象是一个功能完整的对象,所以也能被动态识别(dynamically typed),接收消息,从其他类继承方法。特殊之处在于它们是由编译器创建的,缺少它们自己的数据结构(实例变量),只是在运行时产生实例的代理。

元类

实际上,类对象是元类对象的一个实例!!

元类描述了 一个类对象,就像类对象描述了普通对象一样。不同的是元类的方法列表是类方法的集合,由类对象的选择器来响应。当向一个类发送消息时,objc_msgSend会通过类对象的isa指针定位到元类,并检查元类的方法列表(包括父类)来决定调用哪个方法。元类代替了类对象描述了类方法,就像类对象代替了实例对象描述了实例化方法。

很显然,元类也是对象,也应该是其他类的实例,实际上元类是根元类(root class’s metaclass)的实例,而根元类是其自身的实例,即根元类的isa指针指向自身。

类的superclass指向其父类,而元类的superclass则指向父类的元类。元类的super class链与类的super class链平行,所以类方法的继承与实例方法的继承也是并行的。而根元类(root class’s metaclass)的super_class指向根类(root class),这样,整个指针链就链接起来了!!

一图以蔽之(盗图):



消息转发步骤

消息转发步骤可以理解为objc_msgSend的实现原理。

检测selector是否要被忽略。

检测这个 target 是不是 nil 对象。ObjC 的特性是允许对一个 nil 对象执行任何一个方法不会 Crash,因为会被忽略掉。

如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,完了找得到就跳到对应的函数去执行。

如果 cache 找不到就找一下方法分发表。

如果分发表找不到就到超类的分发表去找,一直找,直到找到NSObject类为止。

如果还找不到就要开始进入动态方法解析。

PS:这里说的分发表其实就是Class中的方法列表,它将方法选择器和方法实现地址联系起来。

一图以蔽之(盗图):



我的理解

Runtime实际上作用跟Java中的反射相类似。

综上所述,类对象(class object)中包含了类的实例变量,实例方法的定义,而元类对象(metaclass object)中包括了类的类方法(也就是C++中的静态方法)的定义。

类对象存的是关于实例对象的信息(变量,实例方法等),而元类对象(metaclass object)中存储的是关于类的信息(类的版本,名字,类方法等)。

类对象(class object)和元类对象(metaclass object)的定义都是objc_class结构,其不同仅仅是在用途上,比如其中的方法列表在类对象(instance object)中保存的是实例方法(instance method),而在元类对象(metaclass object)中则保存的是类方法(class method)

参考资料

objc-runtime源码

Runtime 系列 1– 从一个崩溃谈起

runtime基础篇
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  objective-c