Runtime是什么
将一些在编译、链接过程中的工作,放到运行阶段,因此Objective-C
为动态语言
Runtime
是一个库,这个库使我们可以在程序运行时创建对象、检查对象、修改类和对象的方法。
Runtime是怎么工作的
Class和Object
在objc.h
中,Class
被定义为指向objc_class
的指针,定义如下:
typedef struct objc_class *Class;
而objc_class
是一个结构体,在runtime.h
中的定义如下:
struct objc_class {
// isa指针指向的类结构称为metaclass,其中存放着static类型的成员变量与static类型的方法(“+”开头的方法)
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
// 指向该类的父类的指针,如果该类是根类(如NSObject或NSProxy),那么super_class就为nil。
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,可以通过runtime函数class_setVersion或者class_getVersion进行修改、读取
long info OBJC2_UNAVAILABLE; // 类信息,供运行时期使用的一些位标识、如CLS_CLASS(0x1L)表示该类为普通 class,其中包含实例方法和变量;CLS_META(0x2L)表示该类为metaclass,其中包含类方法;
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小(包括从父类继承下来的实例变量)
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量地址列表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法的地址列表,与info的一些标识位有关,如CLS_CLASS(0x1L)则存储实例方法,如CLS_META(0x2L),则存储类方法;
struct objc_cache *cache OBJC2_UNAVAILABLE; // 缓存最近使用的方法列表,用于提升效率
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 存储该类声明遵守的协议
#endif
} OBJC2_UNAVAILABLE;
一个类包含了自身所有的成员变量(ivars
)、所有的方法(methodLists
)、实现的协议(protocols
)
isa
的定义如下:
struct objc_object {
// 是一个指向objc_class结构体的指针
// objc_object(实例对象)中isa指针指向的类结构称为class(也就是该对象所属的类)其中存放着普通成员变量与动态方法(“-”开头的方法)
Class isa OBJC_ISA_AVAILABILITY;
};
一个对象唯一保存的信息就是它的Class
的地址
调用对象方法的实现过程
- 通过isa去找到对应的
objc_class
; - 在
objc_class
的methodLists
中找到我们调用的方法,然后执行。
Meta Class 元类
Objective-C中,类也被设计为一个对象。
调用对象类方法的实现过程(不考虑继承)
- 通过对象的
isa
指针找到对应的类; - 通过类的
isa
指针找到对应元类; - 在元类的
methodList
中,找到对应的方法,然后执行。
Method
定义如下:
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE; // 方法名
char *method_types OBJC2_UNAVAILABLE; // 方法类型
IMP method_imp OBJC2_UNAVAILABLE; // 方法实现
}
SEL
是一个指向objc_selector
的指针,而非objc_selector
在头文件中找不到明确的定义。不过是一个保存方法名的字符串。
IMP
函数指针:找到函数地址,然后执行函数。
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...)
id
对于实例方法来说,self
保存了当前对象的地址;对于类方法来说,self
保存了当前对应类对象的地址;后面的省略号即是参数列表。
Method
建立了SEL
和IMP
的关联,当对一个对象发送消息时,会通过给出的SEL去找到IMP,然后执行。
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
当向一个对象发送消息时,会去这个类methodLists
中查找对应的SEL
,如果查不到,则通过super_class
指针找到父类,再去父类的methodLists
中查找,层层递进。最后仍然找不到,才走抛异常流程。
拦截调用和消息转发流程
重写
resolveClassMethod:
resolveInstanceMethod:
消息发送
基本的消息发送框架objc_msgSend函数的调用过程:
- 第一步:检测这个
selector
是不是要忽略的。 - 第二步:检测这个
target
是不是nil对象。nil
对象发送任何一个消息都会被忽略掉。 - 第三步:
1.调用实例方法时,它会首先在自身isa
指针指向的类(class
)methodLists
中查找该方法,如果找不到则会通过class
的super_class
指针找到父类的类对象结构体,然后从methodLists
中查找该方法,如果仍然找不到,则继续通过super_class
向上一级父类结构体中查找,直至根class
;
2.当我们调用某个某个类方法时,它会首先通过自己的isa
指针找到metaclass
,并从其中methodLists
中查找该类方法,如果找不到则会通过metaclass
的super_class
指针找到父类的metaclass
对象结构体,然后从methodLists
中查找该方法,如果仍然找不到,则继续通过super_class
向上一级父类结构体中查找,直至根metaclass
; - 第四部:前三部都找不到就会进入动态方法解析(看下文)。
-
第一步:通过
resolveInstanceMethod:
方法决定是否动态添加方法。如果返回Yes则通过class_addMethod
动态添加方法,消息得到处理,结束;如果返回NO
,则进入下一步; -
第二步:这步会进入
forwardingTargetForSelector:
方法,用于指定备选对象响应这个selector
,不能指定为self
。如果返回某个对象则会调用对象的方法,结束。如果返回nil
,则进入第三部; -
第三部:这步我们要通过
methodSignatureForSelector:
方法签名,如果返回nil
,则消息无法处理。如果返回methodSignature`,则进入下一步; -
第四部:这步调用
forwardInvocation
:方法,我们可以通过anInvocation
对象做很多处理,比如修改实现方法,修改响应对象等,如果方法调用成功,则结束。如果失败,则进入doesNotRecognizeSelector
方法,若我们没有实现这个方法,那么就会crash
。
Category
struct objc_category {
char *category_name OBJC2_UNAVAILABLE; // 类别名称
char *class_name OBJC2_UNAVAILABLE; // 类名
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; // 实例方法列表
struct objc_method_list *class_methods OBJC2_UNAVAILABLE; // 类方法列表
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议列表
}
objc_category
中包含对象方法列表、类方法列表、协议列表。
可以通过关联对象的方式给类别添加可用的属性
Runtime的常规操作
-
Method Swizzling
方法交换 - 获取所有属性和方法
Runtime的应用场景
- AOP面向切面编程(对业务逻辑进行分离,降低耦合度)
- 字典转模型
- 进行归解档
- 逆向开发
- 热修复
新手也看得懂的 iOS Runtime 教程
RSSwizzle源码解析
Objective-C Runtime 1小时入门教程
深入理解Objective-C:Category
网友评论