什么是Runtime

Runtime的普遍定义

引用Wiki的定义:

In computer science, runtime, run time, or execution time is the final phase of a computer program’s life cycle, in which the code is being executed on the computer’s central processing unit (CPU) as machine code.

也就是说,Runtime就是程序经过编译之后在CPU上运行的状态。Runtime Library就是程序处于Runtime的时候所能引用的库,Runtime Error就是程序在运行时所得到的错误,常见的有除数为零错误、作用域错误、数组溢出错误等。

Objective-C中的Runtime

C语言中,在编译过程中,编译器就会确定所要调用的函数的位置;

而OC中的函数是动态的,在编译过程中并不能确定要调用函数的位置,因此就需要通过Runtime来动态地创建类和对象以及传递和转发消息。

OC&runtime.png

OC可以通过三种方式和Runtime进行交互:

  1. OC源码

  2. FoundationKit中NSObject协议和类中的如下方法

1
2
3
4
5
6
7
8
9
// @protocol NSObject
- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'type(of: anObject)' instead");
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
- (BOOL)respondsToSelector:(SEL)aSelector;
// @interface NSObject
// 返回制定方法的实现的地址
- (IMP)methodForSelector:(SEL)aSelector;
  1. 对Runtime库的直接调用,主要是<objc/Runtime.h>和<objc/message.h>两个头文件的引入

P.S. 如果需要打开Runtime库的代码提示,需要设置如下位置为NO

XCode

Runtime的用法

通过消息转发实现多继承

metaClass.png

消息转发机制

image-20200910001142554

p.s. 此处要通过转发的方式调用函数,就不能通过类似[myObject testFunc]或者[MyObject testFunc]的方式直接调用,这样在编译的时候就会报错,而是需要用如下方式调用:

1
2
[MyObject performSelector:@selector(testFunc1)]; // 类方法
[myObject performSelector:@selector(testFunc2)]; // 对象方法

类方法的转发

设当前类为MyObject,要调用的类方法为testFunc,即

1
[MyObject performSelector:@selector(testFunc)];
  1. 首先会去MyObject这个类中找testFunc这个方法,如果有则直接返回对应的SEL即可;

  2. 重写+ (BOOL)resolveClassMethod:(SEL)sel方法,该方法返回YES则说明可以为MyObject这个类动态添加一个类方法testFunc,例如:

1
2
3
4
5
6
7
8
9
10
+ (BOOL)resolveClassMethod:(SEL)sel
{
NSLog(@"resolveClassMethod, %@", NSStringFromSelector(sel));
if (sel == @selector(testFunc:Name:)) {
// 如果要在这一步转发,则应当使用class_addMethod
// return class_addMethod(objc_getMetaClass("MyObject"), sel, (IMP)testFunc, "v@:");
return NO;
}
return [super resolveClassMethod:sel];
}

注意此处的函数class_addMethod的参数:

1
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)

第一个参数cls表示当前对象所属的类,由于这是类方法,因此cls应当为当前类的Meta Class;

第二个参数name为当前转发的选择器;

第三个参数imp为要转发到的方法的实现,该方法必须要有两个默认的参数(id self,SEL _cmd)(这两个参数对于每一个NSObject子类中的方法都是已经隐含了的);

第四个参数types为返回类型和方法的参数类型的编码,具体可见Type Encodings

  1. 重写+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法,返回对应selector的signature,示例如下:
1
2
3
4
5
6
7
8
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature* signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return signature;
}
  1. 重写+ (void)forwardInvocation:(NSInvocation *)anInvocation进行最后一步转发,此处可以随意转发到任意类的类方法,示例如下:
1
2
3
4
5
6
7
8
9
10
11
+ (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([MyObject respondsToSelector:@selector(sing)]) {
[anInvocation setSelector:@selector(sing)];
// 注意此处target依然需要为class对象
[anInvocation invokeWithTarget:[MyObject class]];
}
else {
[super forwardInvocation:anInvocation];
}
}
  1. 重写+ (void)doesNotRecognizeSelector:(SEL)aSelector方法,此方法会在找不到对应的selector的最后调用,其super方法会抛出异常,一般不建议覆盖该方法。
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#import <UIKit/UIKit.h>
#import "ViewController.h"
#import <objc/objc.h>
#import <objc/runtime.h>
#import <objc/message.h>
@interface OtherObject : NSObject
+ (void)speak;
@end
@implementation OtherObject
+ (void)speak {
NSLog(@"Speak!!!");
}
@end
@interface MyObject : NSObject
@end
@implementation MyObject
+ (void) sing
{
NSLog(@"Sing");
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
NSLog(@"resolveClassMethod, %@", NSStringFromSelector(sel));
if (sel == @selector(testFunc:Name:)) {
// 如果要在这一步转发,则应当使用class_addMethod
// return class_addMethod(objc_getMetaClass("MyObject"), sel, (IMP)testFunc, "v@:");
return NO;
}
return [super resolveClassMethod:sel];
}
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"forwardingTargetForSelector, %@", NSStringFromSelector(aSelector));
// 如果要在这一步使用OtherObject的类方法(同名)去相应
// if([NSStringFromSelector(aSelector) isEqualToString:@"testFunc:Name:"]){
// return [OtherObject class];
// }
return [super forwardingTargetForSelector:aSelector];
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSLog(@"methodSignatureForSelector, %@", NSStringFromSelector(aSelector));
NSMethodSignature* signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return signature;
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation
{
// if ([OtherObject respondsToSelector:@selector(speak)]) {
// NSLog(@"forwardInvocation YES, %@", anInvocation.target);
// [anInvocation setSelector:@selector(speak)];
// [anInvocation invokeWithTarget:[OtherObject class]];
// }
if ([MyObject respondsToSelector:@selector(sing)]) {
NSLog(@"forwardInvocation YES, %@", anInvocation.target);
[anInvocation setSelector:@selector(sing)];
[anInvocation invokeWithTarget:[MyObject class]];
}
else {
NSLog(@"forwardInvocation NO");
[super forwardInvocation:anInvocation];
}
}
+ (void)doesNotRecognizeSelector:(SEL)aSelector
{
NSLog(@"doesNotRecognizeSelector, %@", NSStringFromSelector(aSelector));
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"RUNTIME";
[MyObject performSelector:@selector(testFunc:Name:)];
}
@end

对象方法的转发

对象方法的转发所涉及的函数和类方法基本类似,只不过都从类方法变成了对象方法(除了resolveClassMethod变成resolveInstanceMethod)

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#import <UIKit/UIKit.h>
#import "ViewController.h"
#import <objc/objc.h>
#import <objc/runtime.h>
#import <objc/message.h>
@interface OtherObject : NSObject
- (void)speak;
@end
@implementation OtherObject
- (void)speak {
NSLog(@"Speak!!!");
}
@end
@interface MyObject : NSObject
@end
@implementation MyObject
- (void) sing
{
NSLog(@"Sing");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"resolveInstanceMethod, %@", NSStringFromSelector(sel));
if (sel == @selector(testFunc:Name:)) {
// 如果要在这一步转发,则应当使用class_addMethod
// return class_addMethod([self class], sel, (IMP)testFunc, "v@:");
return NO;
}
return [super resolveInstanceMethod:sel];
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"forwardingTargetForSelector, %@", NSStringFromSelector(aSelector));
// 如果要在这一步使用OtherObject的对象方法(同名)去相应
// if([NSStringFromSelector(aSelector) isEqualToString:@"testFunc:Name:"]){
// return [[OtherObject alloc] init];
// }
return [super forwardingTargetForSelector:aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSLog(@"methodSignatureForSelector, %@", NSStringFromSelector(aSelector));
NSMethodSignature* signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// OtherObject *otherObject = [[OtherObject alloc] init];
// if ([otherObject respondsToSelector:@selector(speak)]) {
// NSLog(@"forwardInvocation YES, %@", anInvocation.target);
// [anInvocation setSelector:@selector(speak)];
// [anInvocation invokeWithTarget:otherObject];
// }
if ([self respondsToSelector:@selector(sing)]) {
NSLog(@"forwardInvocation YES, %@", anInvocation.target);
[anInvocation setSelector:@selector(sing)];
[anInvocation invokeWithTarget:self];
}
else {
NSLog(@"forwardInvocation NO");
[super forwardInvocation:anInvocation];
}
}
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
NSLog(@"doesNotRecognizeSelector, %@", NSStringFromSelector(aSelector));
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"RUNTIME";
MyObject *myObject = [[MyObject alloc] init];
[myObject performSelector:@selector(testFunc:Name:)];
}
@end

通过消息转发实现的多继承

假设在forwardingTargetForSelector这一步就将MyObject的testFunc: Name: 转发给OtherObject,则:

1
2
MyObject *myObject = [[MyObject alloc] init];
[myObject performSelector:@selector(testFunc:Name:) withObject:@(12) withObject:@"Test"];

上述代码中myObject实际上执行的是OtherObject中的testFunc: Name: 方法。我们可以修改如下函数的代码:

1
2
3
4
5
6
7
8
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return [[OtherObject alloc] init];
}
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
return [OtherObject class];
}

这样在调用MyObject的类方法和对象方法的时候,如果在MyObject类中已经有该方法的实现,则会直接调用该方法的实现;如果没有,则会去OtherObject中寻找该方法的实现。这种模式和MyObject继承自OtherObject是一样的,因此相当于可以用消息转发来实现继承的操作,进而可以实现多继承。

但是这种方法和真正的继承还是不一样的,区别在于调用isKindOfClass函数的时候,真正的继承是可以判断出子类是父类的kindClass,但是消息转发实现的则不能。

Method Swizzling

这是一种OC hook机制的实现

关联对象

用法举例说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@interface NSObject (AssociateObject)

@property (nonatomic, strong) NSString *associatedObject;
@end
@implementation NSObject (AssociateObject)
@dynamic associatedObject;
- (void) setAssociatedObject:(id)associatedObject
{
objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *) associatedObject
{
return objc_getAssociatedObject(self, @selector(associatedObject));
}
@end

涉及到的函数

  1. 设置关联对象
1
OBJC_EXPORT void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy);
  1. 获取关联对象
1
OBJC_EXPORT id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key);
  1. 移除关联对象
1
OBJC_EXPORT void objc_removeAssociatedObjects(id _Nonnull object);

这三个函数中object对应于该关联对象的实例对象,key是用于区分不同的关联对象,可以使用字符串,或者@selector(associatedObject)作为key,value则是关联对象,policy对应的是关联对象的存取策略,与property的attributes一一对应,如下表:

Policy @property
OBJC_ASSOCIATION_ASSIGN @property(assign) / @property(unsafe_unretained)
OBJC_ASSOCIATION_RETAIN_NONATOMIC @property(nonatomic, strong)
OBJC_ASSOCIATION_COPY_NONATOMIC @property(nonatomic, copy)
OBJC_ASSOCIATION_RETAIN @property(atomic, strong)
OBJC_ASSOCIATION_COPY @property(atomic, copy)

P.S. 这里的remove函数是移除所有的关联对象,因此如果要单独删去某一个的话只要用set函数将其置为nil即可。

快速Encode和Decode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super init];
if (self) {
unsigned int cnt = 0;
Ivar *vars = class_copyIvarList([self class], &cnt);
for(int i = 0; i < cnt; i++){
NSString *key = [NSString stringWithUTF8String:ivar_getName(vars[i])];
id value = [coder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
}
return self;
}
- (void)encodeWithCoder:(nonnull NSCoder *)coder {
unsigned int cnt = 0;
Ivar *vars = class_copyIvarList([self class], &cnt);
for(int i = 0; i < cnt; i++){
NSString *key = [NSString stringWithUTF8String:ivar_getName(vars[i])];
id value = [self valueForKey:key];
[coder encodeObject:value forKey:key];
}
}

Tips,现在的类在遵循NSCoding的同时还需要遵循NSSecureCoding,且使用这两个协议的方法如下:

1
2
3
4
5
6
7
8
9
10
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:stu 
requiringSecureCoding:YES
error:nil];
Student *stu2 = [NSKeyedUnarchiver unarchivedObjectOfClasses:
[NSSet setWithObjects:[Student class],
[NSMutableArray class],
[NSMutableDictionary class], nil]
fromData:data
error:nil];
// 切记存在非常规类型的时候需要用unarchivedObjectofClasses这个方法并定义好NSSet类型的classes