Objective-C runtime 的简单理解与使用(一)
简书上的所有内容都可以在我的个人博客上找到(打个广告😅)
一开始接触 iOS 的时候,学的是 Swift。还有一个学期就大四,为了实习,也为了更好的理解 iOS,最近一个多月开始正式学习 Objective-C。而 runtime “运行时” 应该算是 Objective-C 的核心所在。我觉得有必要好好来理解一下 runtime。学的还不是很精,主要是通过看别人的博客和《Effective Objective-C 2.0》这本书来学习 runtime ,官方文档还没有看过,希望以后能够补上。如果有什么理解的不对的地方,希望大家指出。😆
Objective-C中的消息分发
[someObject doSomething];
这是我们在 Objective-C 中调用方法的方式, 但其实这应该称作“传递消息”(pass a message),给 someObject 对象传递一个 doSomething的消息。因为这行代码在编译时会变成这样:
objc_msgSend(someObject, @selector(doSomething))
这是个 C 函数, 声明在 objc/message.h 头文件中,它接受两个及以上的可变参数, 第一个参数代表的是消息的接收者,第二个参数代表选择子(selector,在后面我们会细说),如果要调用的方法还有别的参数,就会跟在这两个参数后面。还有其他几个类似的函数,来处理不同的情况:
id objc_msgSend(id self, SEL op, ...) // 直接发送消息
id objc_msgSendSuper(struct objc_super *super, SEL op, ...) // 给父类发送消息
void objc_msgSend_stret(id self, SEL op, ...) // 返回值是结构体时,可以交给这个函数处理
double objc_msgSend_fpret(id self, SEL op, ...) // 返回值是浮点数时,可以交给这个函数处理
......
当我们向一个对象发送一个消息时,它会在这个对象的类中根据 selector 找到真正的实现函数,如果找不到就再到它的父类中找,一层一层的找上去,最后到 NSObject 类中, 如果还没有找到,那么就会启用“消息转发机制”。这个我们以后再提。
消息分发图示
Objective-C中类的本质
Objective-C 中的类其实是 C 中的结构体,它的基本定义如下:
struct objc_class {
Class isa ;
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 *` */
我们来看一下其中比较重要的几个变量
Class isa
isa 指针,表示这个对象是一个什么类。而 Class 类型, 也就是 struct objc_class *** ,这是苹果在下面的注释中写到的。这说明类本身也是一个对象。在类对象中的 isa 指向的类叫做“元类”**,类方法就定义在元类中。
总的来说就是,一个类可以有很多的实例,这些实例有着唯一的一个类对象,而这个类对象也有着唯一的一个元类。
实例,类,父类,元类之间的关系
Class super_class
super_class 指向的就是它的父类。
struct objc_ivar_list *ivars
ivars 指向的是成员变量的列表。
struct objc_method_list **methodLists
methodLists 指向的就是方法的列表。在 method_list 中存着 objc_method 类型的数组。而 objc_method 的定义如下:
struct objc_method {
SEL method_name ;
char *method_types ;
IMP method_imp ;
}
typedef id (*IMP)(id, SEL, ...); // IMP 就是一个函数指针
看到这个结构体, 我们应该就很明确 selector 的意义了。selector 其实就是方法的一个标示,而 method_imp 指向的才是真正的函数实现。当我们向对象发送消息后, runtime根据 selector 这个标示,在method_list中找到对应的 objc_method,取到真正的函数的地址,再执行。
struct objc_cache *cache
cache 用来缓存最近调用过的的方法。 如果每次向对象发送消息都要遍历一遍方法列表那会很浪费时间, 所以会把最近调用过的方法放在缓存中。每一次发送消息时,会先查询缓存,缓存中找不到再去方法列表中找。
struct objc_protocol_list *protocols
protocols 指向协议列表。
Runtime能做什么
- 动态的改变成员变量的值
- 在运行时添加方法
- 交换方法的实现
- 给一个对象关联其他对象
- 消息转发
- 等等,许许多多我们想不到的功能