Runtime是一个以C和汇编语言编写的库,它是OC面向对象以及动态机制的基石
OC是一门动态化语言,其动态能力包括在运行时创建类和对象、进行消息传递、消息转发。
机器只能识别汇编语言,高级编程语言想要编译成可执行文件需要先转编程汇编,OC是无法直接编译成汇编语言的,需要由C语言进行过渡。我们知道OC是一门面向对象的语言,而C语言更多的是面向过程,所以需要将面向对象的类转变成面向过程的结构体。而实际上OC就是这么做的,我们观察类的源码可以发现它的内部是由许多个结构体组成的。
消息传递
消息传递方法:msg_send(id self, SEL op, …)
。例如一个对象的方法:[person eat]
,运行时编译器转成消息发送msg_send(person, eat)
,过程是这样的:
- 通过person的isa指针找到它的class
- 在它内部的method_list中查找eat方法(类内部有一个cache用于存储调用过的方法,一个类可能会有非常多的方法,而常用的可能就是其中的10%-20%,如果每次都去遍历查找method_list效率太低,所以会对常用的方法进行缓存,以方法名为key,方法的IMP为value,先在缓存中进行查找,若没找到再去method_list中找,这样极大地提升了查找效率)
- 若在class的method_list以及cache中都没有找到,则去superClass中查找,一层层往上查找,直到找到eat为止
- 找到eat后,去执行它对应的IMP
消息转发
上述消息传递到第4部之后,若没有找到对应的selector,runtime就会开始消息转发,一共有三次机会
- 动态方法解析
- 备用接收者
- 完整消息转发
动态方法解析
若没有找到方法,会触发+ (BOOL)resolveInstanceMethod:(SEL)sel
或+ (BOOL)resolveClassMethod:(SEL)sel
方法,在该方法实现中你有机会提供一个方法(函数实现),如果你添加了函数并返回了YES,则运行时系统会重新启动一次消息发送的过程,代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ self.testClass.name = @“Lucifer”; [self performSelector:@selector(runtimeTest)]; }
+ (BOOL)resolveInstanceMethod:(SEL)sel{ NSLog(@“resolveInstanceMethod”); if (sel == NSSelectorFromString(@“runtimeTest”)) { class_addMethod([self class], sel, (IMP)exchangeMethod, “v@:”); } return [super resolveInstanceMethod:sel]; }
void exchangeMethod(id obj, SEL _cmd){ NSLog(@“runtimeTest”); }
|
如果resolve返回NO,运行时会进入到下一步:- (id)forwardingTargetForSelector:(SEL)aSelector
,可以在这一部进行消息接收者替换
备用接收者
如果对象实现了- (id)forwardingTargetForSelector:(SEL)aSelector
,运行时就会给你机会启用一个对象做为备用接收者,将消息转发给它进行处理
实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #import "TestClass.h" @implementation TestClass - (void)runtimeTest { NSLog(@"TestClass runtimeTest"); } @end
#import "ViewController.h" #import "TestClass.h" @implementation ViewController - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [self performSelector:@selector(runtimeTest)]; } - (id)forwardingTargetForSelector:(SEL)aSelector{ if (aSelector == NSSelectorFromString(@"runtimeTest")) { return [TestClass new]; //消息转发给TestClass对象,让textClass对消息进行处理,实现runtimeTest方法 } return [super forwardingTargetForSelector:aSelector]; } @end
|
完整消息转发
如果以上两步还不能处理未知消息,则会启用完整消息转发,这里说的完整消息,就是在触发- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
方法时获取到函数的参数和返回值类型,如果- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
返回 的是nil,runtime会发出doesNotRecognizeSelector:
消息,这时程序崩溃。如果返回了一个函数签名(签名里包含了对象、方法名、方法返回值类型),runtime会创建一个NSInvovation对象,并调用- (void)forwardInvocation:(NSInvocation *)anInvocation
方法,可以在这个方法里进行完整消息转发。代码实现:
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
| #import "TestClass.h" @implementation TestClass - (void)runtimeTest { NSLog(@"TestClass runtimeTest"); } @end
#import "ViewController.h" #import "TestClass.h" @implementation ViewController - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [self performSelector:@selector(runtimeTest)]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ if (aSelector == NSSelectorFromString(@"runtimeTest")) { return [NSMethodSignature signatureWithObjCTypes:"v@:"];// v@:是苹果官方声明的签名 return value @:target ::selector,这里v对应void,@对应self,:对应runtimeTest } return [super methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation{ SEL sel = anInvocation.selector; TestClass *tClass = [[TestClass alloc] init]; if ([tClass respondsToSelector:sel]) { [anInvocation invokeWithTarget:tClass]; //调用tClass去实现selector }else{ [self doesNotRecognizeSelector:sel]; } } @end
|
runtime应用
1 2 3 4 5 6 7 8
| static char KAgeKey; - (void)setAge:(NSString *)age{ objc_setAssociatedObject(self, &KAgeKey, age, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (NSString *)age{ return objc_getAssociatedObject(self, &KAgeKey); }
|
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
| @implementation ViewController
+ (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; SEL originSel = @selector(viewDidLoad); SEL swizzledSel = @selector(newViewDidLoad); Method originalMethod = class_getInstanceMethod(class, originSel); Method swizzledMethod = class_getInstanceMethod(class, swizzledSel); BOOL isAdded = class_addMethod(class, originSel, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (isAdded) { class_replaceMethod(class, swizzledSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); }else{ method_exchangeImplementations(originalMethod, swizzledMethod); } }); }
- (void)viewDidLoad { NSLog(@"viewDidLoad"); [super viewDidLoad]; }
- (void)newViewDidLoad{ NSLog(@"newViewDidLoad"); [self newViewDidLoad]; }
|
- KVO实现
当我们对一个对象A的name属性进行观察时,runtime会动态创建一个A的子类NSKVONotifying_A,并在NSKVONotifying_A中实现name的setter方法,在setter方法中观察name值的变化,并发出通知。那么是如何发出通知的呢,其实是在setter内部添加了willChangeValueForKey:和didChangeValueForKey:方法,代码如下:
1 2 3 4 5 6
| - (void)setName:(NSString *)newName { [self willChangeValueForKey:@"name"]; //KVO 在调用存取方法之前总调用 [super setValue:newName forKey:@"name"]; //调用父类的存取方法 [self didChangeValueForKey:@"name"]; //KVO 在调用存取方法之后总调用 }
|
- 消息转发实现热更新(JSPatch)
- 实现字典转模型(MJExtension实现原理)
通过runtime提供的class_copyPropertyList方法遍历自身素有属性,然后用property_getName、property_getAttributes方法拿到对应属性名和属性类型,最后与json数据进行比对,对model进行赋值。代码如下:
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
| - (instancetype)initWithDict:(NSDictionary *)dict { if (self = [self init]) { //(1)获取类的属性及属性对应的类型 NSMutableArray * keys = [NSMutableArray array]; NSMutableArray * attributes = [NSMutableArray array]; /* * 例子 * name = value3 attribute = T@"NSString",C,N,V_value3 * name = value4 attribute = T^i,N,V_value4 */ unsigned int outCount; objc_property_t * properties = class_copyPropertyList([self class], &outCount); for (int i = 0; i < outCount; i ++) { objc_property_t property = properties[i]; //通过property_getName函数获得属性的名字 NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding]; [keys addObject:propertyName]; //通过property_getAttributes函数可以获得属性的名字和@encode编码 NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding]; [attributes addObject:propertyAttribute]; } //立即释放properties指向的内存 free(properties);
//(2)根据类型给属性赋值 for (NSString * key in keys) { if ([dict valueForKey:key] == nil) continue; [self setValue:[dict valueForKey:key] forKey:key]; } } return self; }
|
[super message]的底层实现
objc_msgSendSuper({self, [Person class]}, @selector(message))
- 消息接受者仍然是子类对象
- 从父类开始查找方法实现