原文连接html
首先,为何说ObjC是动态语言objective-c
咱们看下苹果官方文档对runtime的定义安全
The Objective-C runtime is a runtime library that provides support for the dynamic properties of the Objective-C language, and as such is linked to by all Objective-C apps. Objective-C runtime library support functions are implemented in the shared library found at /usr/lib/libobjc.A.dylib.app
译文以下框架
Objective-C运行时是一个运行时库,它提供对Objective-C语言的动态属性的支持,所以被全部Objective-C应用程序连接。 Objective-C运行时库支持函数在/usr/lib/libobjc.A.dylib中的共享库中实现。ide
在Objective-C中,消息直到运行时才绑定到方法实现。编译器将把方法调用转化为消息发送函数
例如以下代码ui
[receiver message]
复制代码
将会被转化为这种调用方式atom
objc_msgSend(receiver, selector)
复制代码
在消息须要绑定参数的时候会转化以下spa
objc_msgSend(receiver, selector, arg1, arg2, ...)
复制代码
那么抓花为发送消息以后都作了什么呢?
[receiver message]
复制代码
这里咱们发现还缺乏了一种状况,那就是递归在父类的methodlist里面也没有找到对应的实现,这个时候就会报错 unrecognized selector send to instance X
Runtime 为这种可能提供了最后的机会,就是触发消息转发流程
Show Me The Code:
动态添加方法:
#import "AViewController.h"
#import <objc/runtime.h>
@interface AViewController ()
@end
@implementation AViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(speak)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(speak)) {
class_addMethod([self class], sel, (IMP)fakeSpeak, "v@:");
// 关于最后一个参数能够看https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
return true;
}
return [super resolveInstanceMethod:sel];
}
void fakeSpeak(id target, SEL _cmd){
NSLog(@"method added");
}
@end
复制代码
快速转发
#import "AViewController.h"
#import <objc/runtime.h>
@interface AViewController ()
@end
@implementation AViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(speak)];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(speak)) {
return [XXXX new];
}
return nil;
}
@end
复制代码
完整转发
#import "AViewController.h"
#import <objc/runtime.h>
@interface AViewController ()
@end
@implementation AViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(speak)];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(speak)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
[anInvocation setSelector:@selector(otherMethod)];
[anInvocation invokeWithTarget:self];
}
- (void)otherMethod{
NSLog(@"%s",__func__);
}
@end
复制代码
对消息转发的流程有了一些基本概念之后咱们就能够稍微深刻看看方法交换这个理念了。
有的时候咱们可能会面对一些需求,好比在每一个页面中统一都作的一些处理,像访问埋点等逻辑,若是一个一个去改写的话十分麻烦,用继承的方式去作慢慢会产生各类耦合的状况,这里,咱们可使用方法交换的方式去统一添加处理。
好比咱们须要在每个 ViewController viewDidLoad 的方法中输出一个log 先建立一个 category
#import "UIViewController+Log.h"
#import <objc/runtime.h>
@implementation UIViewController (Log)
static void AGExchangeMethod(Class cls, SEL originSelector, SEL newSelector) {
Method originMethod = class_getInstanceMethod(cls, originSelector);
Method newMethod = class_getInstanceMethod(cls, newSelector);
// method_exchangeImplementations(newMethod, originMethod);
BOOL addMethod = class_addMethod(cls, originSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod));
if (addMethod) {
class_replaceMethod(cls, newSelector, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
}else {
method_exchangeImplementations(newMethod, originMethod);
}
}
+ (void)load {
static dispatch_once_t once;
dispatch_once(&once, ^{
AGExchangeMethod([self class], @selector(viewDidLoad), @selector(Logging));
});
}
- (void)Logging{
NSLog(@"%s",__func__);
[self Logging];
}
@end
复制代码
编译运行,你能够看到控制台会输出 Logging
这里有几个地方须要特别留意下
load
中, 该方法会在类被加载的时候执行好比咱们想要为 UIViewController 添加一个flag属性记录状态,可是没法更改 UIViewController,那么咱们能够在 category 中添加属性
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIViewController (Log)
@property (nonatomic ,copy) NSString *flag;
@end
NS_ASSUME_NONNULL_END
复制代码
而后在其余的 viewController 中使用
- (void)viewDidLoad {
[super viewDidLoad];
self.flag = @"active";
}
复制代码
运行后能够看到崩溃 unrecognized selector sent to instance
, 这是由于在 category 中 property修饰符并不会自动为咱们生成成员变量,而咱们知道,属性实际上是 ivar + getter & setter ,因此咱们可使用 runtime 来手动关联:
在 category 的 .m 文件中增长如下代码
- (void)setFlag:(NSString *)flag {
objc_setAssociatedObject(self, @selector(flag), flag, OBJC_ASSOCIATION_COPY);
}
- (NSString *)flag {
return objc_getAssociatedObject(self, _cmd);
}
复制代码
而后就能够在其余 viewController 中随意使用了,因为 objc_setAssociatedObject 也是在ARC管理之下的因此咱们也没必要手动释放。
虽然 Runtime 有诸多魔幻的使用方法,可是不建议过多的使用(除非掌握的很熟练),除非是开发框架,不然多个互相交换的方法和动态的属性在调试的时候会很无奈的。。。