什么是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进行交互:
OC源码
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;
对Runtime库的直接调用,主要是<objc/Runtime.h>和<objc/message.h>两个头文件的引入
P.S. 如果需要打开Runtime库的代码提示,需要设置如下位置为NO
Runtime的用法
通过消息转发实现多继承
消息转发机制
p.s. 此处要通过转发的方式调用函数,就不能通过类似[myObject testFunc]或者[MyObject testFunc]的方式直接调用,这样在编译的时候就会报错,而是需要用如下方式调用:
1 2 [MyObject performSelector:@selector(testFunc1)]; // 类方法 [myObject performSelector:@selector(testFunc2)]; // 对象方法
类方法的转发
设当前类为MyObject,要调用的类方法为testFunc,即
1 [MyObject performSelector:@selector(testFunc)];
首先会去MyObject这个类中找testFunc这个方法,如果有则直接返回对应的SEL即可;
重写+ (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
重写+ (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; }
重写+ (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]; } }
重写+ (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 OBJC_EXPORT void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy);
获取关联对象
1 OBJC_EXPORT id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key);
移除关联对象
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