深入浅出Runtime (一) 什么是Runtime? 定义?
深入浅出 Runtime详解
Runtime是什么?
- 运行时(Runtime)是指将数据类型的确定由编译时推迟到了运行时
- Runtime是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API
- 平时编写的OC代码,在程序运行过程中,其实最终会转换成Runtime的C语言代码,Runtime是Object-C的幕后工作者
- Object-C需要Runtime来创建类和对象,进行消息发送和转发
特性: 编写的代码具有运行时、动态特性
Runtime用来干什么?用在哪些地方?
用来干什么 基本作用
- 在程序运行过程中,动态的创建类,动态添加、修改这个类的属性和方法;
- 遍历一个类中所有的成员变量、属性、以及所有方法
- 消息传递、转发
用在哪些地方 Runtime的典型事例
- 给系统分类添加属性、方法
- 方法交换
- 获取对象的属性、私有属性
- 字典转换模型
- KVC、KVO
- 归档(编码、解码)
- NSClassFromString class<->字符串
- block
- 类的自我检测
- ...
Runtime的定义
在Object-C中的NSObject对象中
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
上述表述Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
由此可见可以看到Class
、id
前者是类,后者指向类的指针,id
是指向objc_object
的一个指针,而objc_object
有个isa
指向objc_class
的一个指针
So,不管id
,还是Class
最后指向的都是objc_class
这个结构体
objc_class
结构体中的定义如下:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
在runtime使用当中,我们经常需要用到的字段,它们的定义
-
isa
Class
对象,指向objc_class
结构体的指针,也就是这个Class
的MetaClass
(元类)
- 类的实例对象的 isa 指向该类;该类的 isa 指向该类的 MetaClass
- MetaCalss的isa对象指向RootMetaCalss
- 如果MetaClass是RootMetaCalss,那么该MetaClass的isa指针指向它自己 -
super_class Class对象指向父类对象
- 如果该类的对象已经是RootClass,那么这个super_class
指向nil
- MetaCalss的SuperClass指向父类的MetaCalss
- MetaCalss是RootMetaCalss,那么该MetaClass的SuperClass指向该对象的RootClass
一张图可以完美的解释这个知识点
-
ivars 类中所有属性的列表,使用场景:我们在字典转换成模型的时候需要用到这个列表找到属性的名称,去取字典中的值,KVC赋值,或者直接Runtime赋值
-
methodLists 类中所有的方法的列表,类中所有方法的列表,使用场景:如在程序中写好方法,通过外部获取到方法名称字符串,然后通过这个字符串得到方法,从而达到外部控制App已知方法。
-
cache 主要用于缓存常用方法列表,每个类中有很多方法,我平时不用的方法也会在里面,每次运行一个方法,都要去
methodLists
遍历得到方法,如果类的方法不多还行,但是基本的类中都会有很多方法,这样势必会影响程序的运行效率,所以cache
在这里就会被用上,当我们使用这个类的方法时先判断cache
是否为空,为空从methodLists
找到调用,并保存到cache
,不为空先从cache
中找方法,如果找不到在去methodLists
,这样提高了程序方法的运行效率 -
protocols 故名思义,这个类中都遵守了哪些协议,使用场景:判断类是否遵守了某个协议上
深入Runtime的运行原理
当我写到深入Runtime的运行原理的时候,脑海中冒出的想法是怎么深入,从哪里开始挖掘runtime的内容:
第一个想法就是
- 介绍runtime的几个方法
- runtime的使用方法
- runtime的实际操作场景、应用
- runtime的总结
如果说这些就是深入runtime,就是调用下api
然后想了很久,每次都会想就直接介绍使用,总结完事;心里贼不得劲
不能就这么简简单单了事,那么开始想到哪点,深入做哪点深入
大纲(后续会补充):
- 类底层代码、类的本质?
- 类底层是如何调用方法?
- Runtime消息传递
- Runtime消息转发
- Runtime起到了什么作用?
- Runtime实际应用
类底层代码、类的本质?
为了更好的认识类是怎么工作的,我们将要将一段Object-C的代码用clang
看下底层的C/C++的写法
typedef enum : NSUInteger {
ThisRPGGame = 0,
ThisActionGame = 1,
ThisBattleFlagGame = 2,
} ThisGameType;
@interface Game : NSObject
@property (copy,nonatomic)NSString *Name;
@property (assign,nonatomic)ThisGameType Type;
@end
@implementation Game
@synthesize Name,Type;
- (void)GiveThisGameName:(NSString *)name{
Name = name;
}
- (void)GiveThisGameType:(ThisGameType)type{
Type = type;
}
@end
使用命令,在当前文件夹中会出现Game.cpp的文件
# clang -rewrite-objc Game.m
Game.cpp
由于生成的文件很庞大,可以仔细去研读,受益匪浅
研读方式:如果按照从上往下的顺序去研读,会很不理解,所以我的研读方式从关键点切入首先理解关键的几点,然后在慢慢抛析
/*
* 顾名思义存放property的结构体
* 当我们使用perproty的时候,会生成这样一个结构体
* 具体存储的数据为
* 实际内容:"Name","T@\"NSString\",C,N,VName"
* 原型:@property (copy,nonatomic)NSString *Name;
* 这个具体是怎么实现的,我会在后面继续深入研究,本文主要来理解runtime的理解
**/
struct _prop_t {
const char *name; //名字
const char *attributes; //属性
};
/*
*类中方法的结构体,cmd和imp的关系是一一对应的关系
*创建对象生成isa指针,指向这个对象的结构体时
*同时生成了一个表"Dispatch table"通过这个_cmd的编号找到对应方法
*使用场景:
*例如方法交换,方法判断。。。
**/
struct _objc_method {
struct objc_selector * _cmd; //SEL 对应着OC中的@selector()
const char *method_type; //方法的类型
void *_imp; //方法的地址
};
/*
* method_list_t 结构体:
* 原型:
* - (void)GiveThisGameName:(NSString *)name;
* 实际存储的方式:
* {(struct objc_selector *)"GiveThisGameName:", "v24@0:8@16", (void *)_I_Game_GiveThisGameName_}
* 其主要目的是存储一个数组,基本的数据类型是 _objc_method
* 扩展:当然这其中有你的属性,自动生成的setter、getter方法
**/
static struct _method_list_t {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[6];
}
/*
* 表示这个类中所遵守的协议对象
* 使用场景:
* 判断类是否遵守这个协议,从而动态添加、重写、交换某些方法,来达到某些目的
*
**/
struct _protocol_t {
void * isa; // NULL
const char *protocol_name;
const struct _protocol_list_t * protocol_list; // super protocols
const struct method_list_t *instance_methods; // 实例方法
const struct method_list_t *class_methods; //类方法
const struct method_list_t *optionalInstanceMethods; //可选的实例方法
const struct method_list_t *optionalClassMethods; //可选的类方法
const struct _prop_list_t * properties; //属性列表
const unsigned int size; // sizeof(struct _protocol_t)
const unsigned int flags; // = 0
const char ** extendedMethodTypes; //扩展的方法类型
};
/*
* 类的变量的结构体
* 原型:
* NSString *Name;
* 存储内容:
* {(unsigned long int *)&OBJC_IVAR_$_Game$Name, "Name", "@\"NSString\"", 3, 8}
* 根据存储内容可以大概了解这些属性的工作内容
**/
struct _ivar_t {
unsigned long int *offset; // pointer to ivar offset location
const char *name; //名字
const char *type; //属于什么变量
unsigned int alignment; //未知
unsigned int size; //大小
};
/*
* 这个就是类中的各种方法、属性、等等信息
* 底层也是一个结构体
* 名称、方法列表、协议列表、变量列表、layout、properties。。
*
**/
struct _class_ro_t {
unsigned int flags;
unsigned int instanceStart;
unsigned int instanceSize;
unsigned int reserved;
const unsigned char *ivarLayout; //布局
const char *name; //名字
const struct _method_list_t *baseMethods;//方法列表
const struct _objc_protocol_list *baseProtocols; //协议列表
const struct _ivar_list_t *ivars; //变量列表
const unsigned char *weakIvarLayout; //弱引用布局
const struct _prop_list_t *properties; //属性列表
};
/*
* 类本身
* oc在创建类的时候都会创建一个 _class_t的结构体
* 我的理解是在runtime中的object-class结构体在底层就会变成_class_t结构体
**/
struct _class_t {
struct _class_t *isa; //元类的指针
struct _class_t *superclass; //父类的指针
void *cache; //缓存
void *vtable; //表信息、未知
struct _class_ro_t *ro; //这个就是类中的各种方法、属性、等等信息
};
/*
* 类扩展的结构体
* 在OC中写的类扩展
**/
struct _category_t {
const char *name; //名称
struct _class_t *cls; //这个是哪个类的扩展
const struct _method_list_t *instance_methods; //实例方法列表
const struct _method_list_t *class_methods; //类方法列表
const struct _protocol_list_t *protocols; //协议列表
const struct _prop_list_t *properties; //属性列表
};
上述是Object-C中类中基本的数据,了解了类的定义,我们基本可以这么理解,类就是多个结构体组合的一个集合体,类中的行为、习惯、属性抽象,按照机器能懂的数据存储到我们底层的结构体当中,在我们需要使用的时候直接获取使用。
那么就开始研究一下,类是如何使用,类的基本使用过程以及过程中runtime所做的事情。
类底层是如何调用方法?
了解了类的组成,那么类是通过什么样的形式去获取方法属性并得到应用?
在Object-C开发中我们经常会说到,对象调用方法,其本质就是想这个对象发送消息,为什么会有这么一说?下面我们来验证一下
Object-C代码
int main(int argc, char * argv[]) {
Game *game = [Game alloc];
[game init];
[game Play];
return 0;
}
底层代码的实现
int main(int argc, char * argv[]) {
Game *game = ((Game *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Game"), sel_registerName("alloc"));
game = ((Game *(*)(id, SEL))(void *)objc_msgSend)((id)game, sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)game, sel_registerName("Play"));
return 0;
}
代码中使用了
-
objc_msgSend
消息发送 -
objc_getClass
获取对象 -
sel_registerName
获取方法的SEL
因为目前重点是objc_msgSend
,其他的Runtime的方法会在后面继续一一道来, So 一个对象调用其方法,在Object-C中就是向这个对象发送一条消息,消息的格式
objc_msgSend("对象","SEL","参数"...)
objc_msgSend( id self, SEL op, ... )
待续
消息机制
总结
Rumtime是Objective-C语言动态的核心,Objective-C的对象一般都是基于Runtime的类结构,达到很多在编译时确定方法推迟到了运行时,从而达到动态修改、确定、交换...属性及方法