原文連接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 有諸多魔幻的使用方法,可是不建議過多的使用(除非掌握的很熟練),除非是開發框架,不然多個互相交換的方法和動態的屬性在調試的時候會很無奈的。。。