学习Objective-C
的运行时Runtime
系统是颇有必要的。我的以为,得之可得天下,失之则失天下。编程
Objective-C
提供了编译运行时,只要有可能,它均可以动态地运做。这意味着不只须要编译器,还须要运行时系统执行编译的代码。运行时系统充当Objective-C
语言的操做系统,有了它才能运做。数组
运行时系统所提供功能是很是强大的,在实际开发中是常用到的。好比,苹果不容许咱们给Category
追加扩展属性,是由于它不会自动生成成员变量,那么咱们经过运行时就能够很好的解决这个问题。另外,常见的模型转字典或者字典转模型,对象归档等。后续咱们再来学习如何应用,本节只是讲讲理论。缓存
Objective-C
程序有有三种与runtime
系统交互的级别:数据结构
Objective-C
源代码Foundation
库中定义的NSObject
提供的方法runtime
方法在大多数的部分,运行时系统会自动运行并在后台运行。咱们使用它只是写源代码并编译源代码。当编译包含Objective-C
类和方法的代码时,编译器会建立实现了语言动态特性的数据结构和函数调用。该数据结构捕获在类、扩展和协议中所定义的信息。app
最重要的runtime
函数是发消息函数,在编译时,编译器会转换成相似objc_msgSend
这样的发送消息的函数。所以,咱们经过写好源代码,编译器会自动帮助咱们编译成runtime
代码。函数
在Cocoa
编程中,大部分的类都继承于NSObject
,也就是说NSObject
一般是根类,大部分的类都继承于NSObject
。有些特殊的状况下,NSObject
只是提供了它应该要作什么的模板,却没有提供全部必须的代码。学习
有些NSObject
提供的方法仅仅是为了查询运动时系统的相关信息,这此方法均可以反查本身。好比-isKindOfClass:
和-isMemberOfClass:
都是用于查询在继承体系中的位置。-respondsToSelector:
指明是否接受特定的消息。+conformsToProtocol:
指明是否要求实如今指定的协议中声明的方法。-methodForSelector:
提供方法实现的地址。this
runtime
库函数在usr/include/objc
目录下,咱们主要关注是这两个头文件:编码
1
2
3
4
|
#import <objc/runtime.h>
#import <objc/objc.h>
|
关于如何使用,后续的文章再细细讲解。spa
为何叫消息呢?由于面向对象编程中,对象调用方法叫作发送消息。在编译时,应用的源代码就会被编将对象发送消息转换成runtime
的objc_msgSend
函数调用。
在Objective-C
,消息在运行时并不要求实现。编译器会转换消息表达式:
1
2
3
|
[receiver message];
|
在编译时会转换成相似这样的函数调用:
1
2
3
|
objc_msgSend(receiver, selector);
|
具体会转换成哪一个,咱们来看看官方的原话:
1
2
3
4
5
6
7
|
When it encounters a method call, the compiler generates a call to one of the
* functions \c objc_msgSend, \c objc_msgSend_stret, \c objc_msgSendSuper, or \c objc_msgSendSuper_stret.
* Messages sent to an object’s superclass (using the \c super keyword) are sent using \c objc_msgSendSuper;
* other messages are sent using \c objc_msgSend. Methods that have data structures as return values
* are sent using \c objc_msgSendSuper_stret and \c objc_msgSend_stret.
|
也就是说,咱们是经过编译器来自动转换成运行时代码时,它会根据类型自动转换成下面的其它一个函数:
另外,若是函数返回值是浮点类型,官方说明以下:
1
2
3
4
5
6
7
8
9
|
* arm: objc_msgSend_fpret not used
* i386: objc_msgSend_fpret used for `float`, `double`, `long double`.
* x86-64: objc_msgSend_fpret used for `long double`.
*
* arm: objc_msgSend_fp2ret not used
* i386: objc_msgSend_fp2ret not used
* x86-64: objc_msgSend_fp2ret used for `_Complex long double`.
|
其实这是一个条件编译,咱们不用担忧是哪一种处理器上,咱们只须要调用objc_msgSend_fpret
函数便可。
注意事项:必定要调用所调用的API支持哪些平台,乱调在致使部分平台上不支持而崩溃的。
当消息被发送到实例对象时,它是如何处理的:
咱们的根类是NSObject
,它会一层一层的传递,直接找到要处理该消息的对象,若都没有找到,正常状况下会出现Unreconized selector ...
这样的崩溃提示了。
当发送消息给一个不处理该消息的对象是错误的。而后在宣布错误以前,运行时系统给了接收消息的对象处理消息的第二个机会。
当某对象不处理某消息时,能够经过重写-forwardInvocation:
方法来提供一个默认的消息响应或者避免出错。当对象中找不到方法实现时,会按照类继承关系一层层往上找。咱们看看类继承关系图:
全部元类中的isa
指针都指向根元类,而根元类的isa
指针则指向自身。根元类是继承于根类的,与根类的结构体成员一致,都是objc_class结构体,不一样的是根元类的isa
指针指向自身,而根类的isa
指针为nil
咱们再看看消息处理流程:
当对象查询不到相关的方法,消息得不到该对象处理,会启动“消息转发”机制。消息转发还分为几个阶段:先询问receiver
或者说是它所属的类是否能动态添加方法,以处理当前这个消息,这叫作“动态方法解析”,runtime会经过+resolveInstanceMethod:
判断可否处理。若是runtime完成动态添加方法的询问以后,receiver
仍然没法正常响应则Runtime会继续向receiver询问是否有其它对象即其它receiver能处理这条消息,若返回可以处理的对象,Runtime会把消息转给返回的对象,消息转发流程也就结束。若无对象返回,Runtime会把消息有关的所有细节都封装到NSInvocation
对象中,再给receiver
最后一次机会,令其设法解决当前还未处理的这条消息。
提示:消息处理越日后,开销也就会越大,所以最好直接在第一步就能够获得消息处理。
咱们看看类结构体:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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;
/* Use `Class` instead of `struct objc_class *` */
|
咱们能够看到每一个类结构体都会有一个isa
指针,它是指向元类的。它还有一个父类指针super_class
,指针父类。包含了类的名称name
、类的版本信息version
、类的一些标识信息info
、实例大小instance_size
、成员变量地址列表ivars
、方法地址列表methodLists
、缓存最近使用的方法地址cache
、协议列表protocols`。
咱们在使用时,常用到Class
,它就是:
1
2
3
|
typedef struct objc_class *Class;
|
当类为根类时,它的super_class
就会是nil
。普通的Class
存储的是实例成员,如-
号方法、属性、成员变量,而isa
则指向元类,而元类存储的是静态成员,如+
号方法、static
成员。
编码值 | 含意 |
---|---|
c | 表明char类型 |
i | 表明int类型 |
s | 表明short类型 |
l | 表明long类型,在64位处理器上也是按照32位处理 |
q | 表明long long类型 |
C | 表明unsigned char类型 |
I | 表明unsigned int类型 |
S | 表明unsigned short类型 |
L | 表明unsigned long类型 |
Q | 表明unsigned long long类型 |
f | 表明float类型 |
d | 表明double类型 |
B | 表明C++中的bool或者C99中的_Bool |
v | 表明void类型 |
* | 表明char *类型 |
@ | 表明对象类型 |
# | 表明类对象 (Class) |
: | 表明方法selector (SEL) |
[array type] | 表明array |
{name=type…} | 表明结构体 |
(name=type…) | 表明union |
bnum | A bit field of num bits |
type | A pointer to type |
? | An unknown type (among other things, this code is used for function pointers) |
咱们想要经过运行时处理各类类型,那么咱们必需要知道哪一种字符表明什么类型。