Objective-C執行時特性:Method Swizzling魔法
OC執行時特性,為我們提供了一個叫做Method Swizzling的方法魔法利器,利用它我們可以更加隨心所欲的在執行時期間對編譯器已經的方法再次動手腳,主要包括:交換類中某兩個方法的實現、重新新增或替換某個方法的具體實現。
執行時的幾種特殊型別
Class: 類名,通過類的class類方法獲得,例如:[UIViewController class];
SEL:選擇器,也就是方法名,通過@selector(方法名:)獲得,例如:@selector(buttonClicked:);
Method:方法,即執行時類中定義的方法,包括方法名(SEL)和方法實現(IMP)兩部分,通過執行時方法class_getInstanceMethod或class_getClassMethod獲得;
IMP:方法實現型別,指的是方法的實現部分,通過執行時方法class_getMethodImplementation或method_getImplementation獲得;
替換類中某兩個類方法或例項方法的實現
關鍵執行時函式:method_exchangeImplementations(method1, method2)
這裡隨便定義一個Test類,類中定義兩個例項方法和類方法並在.m檔案中實現,在執行時將兩個例項方法的實現對調,以及將兩個類方法的實現對調。注意執行時程式碼寫在類的load方法內,該方法只會在該類第一次載入時呼叫一次,且寫執行時程式碼的地方需要引入執行時標頭檔案#import <objc/runtime.h>。
Test類定義:
#import <Foundation/Foundation.h>
@interface Test : NSObject
/** * 定義兩個公有例項方法 */
- (void)instanceMethod1;
- (void)instanceMethod2;
/** * 定義兩個公有類方法 */
+ (void)classMethod1;
+ (void)classMethod2;
@end
#import "Test.h" #import <objc/runtime.h>
@implementation Test
/** * runtime程式碼寫在類第一次調載入的時候(load方法有且只有一次會被呼叫) */
+ (void)load {
/* 1. 獲取當前類名 */
Class class = [self class];
/* 2. 獲取方法名(選擇器) */
SEL selInsMethod1 = @selector(instanceMethod1);
SEL selInsMethod2 = @selector(instanceMethod2);
SEL selClassMethod1 = @selector(classMethod1);
SEL selClassMethod2 = @selector(classMethod2);
/* 3. 根據方法名獲取方法物件 */
Method InsMethod1 = class_getInstanceMethod(class, selInsMethod1);
Method InsMethod2 = class_getInstanceMethod(class, selInsMethod2);
Method ClassMethod1 = class_getClassMethod(class, selClassMethod1);
Method ClassMethod2 = class_getClassMethod(class, selClassMethod2);
/* 4. 交換例項方法的實現和類方法的實現 */
if (!InsMethod1 || !InsMethod2) {
NSLog(@"例項方法實現執行時交換失敗!");
return;
}
/* 交換例項方法的實現 */
method_exchangeImplementations(InsMethod1, InsMethod2);
if (!ClassMethod1 || !ClassMethod2) {
NSLog(@"類方法實現執行時交換失敗!");
return;
}
/* 交換類方法的實現 */
method_exchangeImplementations(ClassMethod1, ClassMethod2);
}
/** * 例項方法的原實現 */
- (void)instanceMethod1 {
NSLog(@"instanceMethod1...");
}
- (void)instanceMethod2 {
NSLog(@"instanceMethod2...");
}
/** * 類方法的原實現 */
+ (void)classMethod1 {
NSLog(@"classMethod1...");
}
+ (void)classMethod2 {
NSLog(@"classMethod2...");
}
@end
測試程式碼:
#import "ViewController.h" #import "Test.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
/* 測試類方法呼叫 */
[Test classMethod1];
[Test classMethod2];
Test *test = [[Test alloc] init];
/* 測試例項方法呼叫 */
[test instanceMethod1];
[test instanceMethod2];
}
@end
通過下面的輸出結果可知,兩個例項方法和類方法的實現都被互換了:
2017-03-06 17:47:13.684 SingleView[41495:1196960] classMethod2...
2017-03-06 17:47:13.684 SingleView[41495:1196960] classMethod1...
2017-03-06 17:47:13.685 SingleView[41495:1196960] instanceMethod2...
2017-03-06 17:47:13.685 SingleView[41495:1196960] instanceMethod1...
重新設定類中某個方法的實現
關鍵執行時函式:method_setImplementation(method, IMP)
理解了上面的例子,我們現在略微修改其中執行時程式碼,通過重新設定方法的實現實現上面同樣的效果
修改後的執行時程式碼為:
/**
* runtime程式碼寫在類第一次調載入的時候(load方法有且只有依次會被呼叫)
*/
+ (void)load {
/* 1. 獲取當前類名 */
Class class = [self class];
/* 2. 獲取方法名(選擇器) */
SEL selInsMethod1 = @selector(instanceMethod1);
SEL selInsMethod2 = @selector(instanceMethod2);
SEL selClassMethod1 = @selector(classMethod1);
SEL selClassMethod2 = @selector(classMethod2);
/* 3. 根據方法名獲取方法物件 */
Method InsMethod1 = class_getInstanceMethod(class, selInsMethod1);
Method InsMethod2 = class_getInstanceMethod(class, selInsMethod2);
Method ClassMethod1 = class_getClassMethod(class, selClassMethod1);
Method ClassMethod2 = class_getClassMethod(class, selClassMethod2);
/* 下面程式碼為修改部分... ... */
/* 4. 獲取方法的實現 */
IMP impInsMethod1 = method_getImplementation(InsMethod1);
IMP impInsMethod2 = method_getImplementation(InsMethod2);
IMP impClassMethod1 = method_getImplementation(ClassMethod1);
IMP impClassMethod2 = method_getImplementation(ClassMethod2);
/* 5. 重新設定方法的實現 */
/* 重新設定instanceMethod1的實現為instanceMethod2的實現 */
method_setImplementation(InsMethod1, impInsMethod2);
/* 重新設定instanceMethod2的實現為instanceMethod1的實現 */
method_setImplementation(InsMethod2, impInsMethod1);
/* 重新設定classMethod1的實現為classMethod2的實現 */
method_setImplementation(ClassMethod1, impClassMethod2);
/* 重新設定classMethod2的實現為classMethod1的實現 */
method_setImplementation(ClassMethod2, impClassMethod1);
}
執行後列印結果和上面方法實現交換的例子結果相同:
2017-03-06 18:27:53.032 SingleView[41879:1212691] classMethod2...
2017-03-06 18:27:53.032 SingleView[41879:
1212691] classMethod1...
2017-03-06 18:27:53.033 SingleView[41879:1212691] instanceMethod2...
2017-03-06 18:27:53.033 SingleView[41879:1212691] instanceMethod1...
替換類中某個方法的實現
關鍵執行時函式:class_replaceMethod
這種方法只能替換例項方法的實現,而不能替換類方法的實現,修改的程式碼如下
/**
* runtime程式碼寫在類第一次調載入的時候(load方法有且只有依次會被呼叫)
*/
+ (void)load {
/* 1. 獲取當前類名 */
Class class = [self class];
/* 2. 獲取方法名(選擇器) */
SEL selInsMethod1 = @selector(instanceMethod1);
SEL selInsMethod2 = @selector(instanceMethod2);
SEL selClassMethod1 = @selector(classMethod1);
SEL selClassMethod2 = @selector(classMethod2);
/* 3. 根據方法名獲取方法物件 */
Method InsMethod1 = class_getInstanceMethod(class, selInsMethod1);
Method InsMethod2 = class_getInstanceMethod(class, selInsMethod2);
Method ClassMethod1 = class_getClassMethod(class, selClassMethod1);
Method ClassMethod2 = class_getClassMethod(class, selClassMethod2);
/* 4. 獲取方法的實現 */
IMP impInsMethod1 = method_getImplementation(InsMethod1);
IMP impInsMethod2 = method_getImplementation(InsMethod2);
IMP impClassMethod1 = method_getImplementation(ClassMethod1);
IMP impClassMethod2 = method_getImplementation(ClassMethod2);
/* 下面程式碼為修改部分... ... */
/* 5. 獲取方法編碼型別 */
const char* typeInsMethod1 = method_getTypeEncoding(InsMethod1);
const char* typeInsMethod2 = method_getTypeEncoding(InsMethod2);
const char* typeClassMethod1 = method_getTypeEncoding(ClassMethod1);
const char* typeClassMethod2 = method_getTypeEncoding(ClassMethod2);
/* 替換InsMethod1的實現為InsMethod2的實現 */
class_replaceMethod(class, selInsMethod1, impInsMethod2, typeInsMethod2);
/* 替換InsMethod2的實現為InsMethod1的實現 */
class_replaceMethod(class, selInsMethod2, impInsMethod1, typeInsMethod1);
class_replaceMethod(class, selClassMethod1, impClassMethod2, typeClassMethod2);
class_replaceMethod(class, selClassMethod2, impClassMethod1, typeClassMethod1);
}
通過結果可見例項方法的實現成功被替換,而類方法的實現沒有被替換:
2017-03-06 18:47:03.598 SingleView[42106:1221468] classMethod1...
2017-03-06 18:47:03.599 SingleView[42106:1221468] classMethod2...
2017-03-06 18:47:03.600 SingleView[42106:1221468] instanceMethod2...
2017-03-06 18:47:03.600 SingleView[42106:1221468] instanceMethod1...
以上介紹的是同一個類中方法實現的再改動,實際上也可以修改或交換不同類之間方法的實現。
在執行時為類補加新的方法
關鍵執行時函式:class_addMethod()
除了在編譯期顯式的定義方法,還可以在執行時補加新的例項方法,但不可以新增新的類方法,這裡接上面的例子為Test在執行時新增一個新的名為newInsMethod的方法,方法的實現設定為InsMethod1的實現,修改後的執行時程式碼為:
/**
* runtime程式碼寫在類第一次調載入的時候(load方法有且只有依次會被呼叫)
*/
+ (void)load {
/* 1. 獲取當前類名 */
Class class = [self class];
/* 2. 獲取方法名(選擇器) */
SEL selInsMethod1 = @selector(instanceMethod1);
SEL selInsMethod2 = @selector(instanceMethod2);
SEL selClassMethod1 = @selector(classMethod1);
SEL selClassMethod2 = @selector(classMethod2);
/* 3. 根據方法名獲取方法物件 */
Method InsMethod1 = class_getInstanceMethod(class, selInsMethod1);
Method InsMethod2 = class_getInstanceMethod(class, selInsMethod2);
Method ClassMethod1 = class_getClassMethod(class, selClassMethod1);
Method ClassMethod2 = class_getClassMethod(class, selClassMethod2);
/* 4. 獲取方法的實現 */
IMP impInsMethod1 = method_getImplementation(InsMethod1);
IMP impInsMethod2 = method_getImplementation(InsMethod2);
IMP impClassMethod1 = method_getImplementation(ClassMethod1);
IMP impClassMethod2 = method_getImplementation(ClassMethod2);
/* 5. 獲取方法編碼型別 */
const char* typeInsMethod1 = method_getTypeEncoding(InsMethod1);
const char* typeInsMethod2 = method_getTypeEncoding(InsMethod2);
const char* typeClassMethod1 = method_getTypeEncoding(ClassMethod1);
const char* typeClassMethod2 = method_getTypeEncoding(ClassMethod2);
/* 下面程式碼為修改部分... ... */
/* 6. 為類新增新的例項方法和類方法 */
SEL selNewInsMethod = @selector(newInsMethod);
BOOL isInsAdded = class_addMethod(class, selNewInsMethod, impInsMethod1, typeInsMethod1);
if (!isInsAdded) {
NSLog(@"新例項方法新增失敗!");
}
}
測試新函式程式碼
/* 測試執行時新新增例項方法呼叫 */
Test *test = [[Test alloc] init];
[test newInsMethod];
執行結果列印出“instanceMethod1…”證明新例項方法動態新增成功:
2017-03-06 19:07:15.447 SingleView[42354:1230571] instanceMethod1...
除了在執行時為類新增新的方法,還可以通過其他執行時函式class_addIvar、class_addProperty、class_addProtocol等動態地為類新增新的變數、屬性和協議等等。
相關文章
- Objective-C Runtime (三):Method Swizzling(方法替換)Object
- Swizzling Method
- Runtime-(六)Method-Swizzling
- Swift 5 之後 "Method Swizzling"?Swift
- Objective-C Runtime(四)isa swizzlingObject
- 關於 Method Swizzling 的一點思考
- iOS逆向之旅(進階篇) — HOOK(Method Swizzling)iOSHook
- iOS 開發:『Runtime』詳解(二)Method SwizzlingiOS
- iOS開發之 Method Swizzling 深入淺出iOS
- ios runtime之Method Swizzling及其應用場景iOS
- Method Swizzling 為什麼要先呼叫 class_addMethod?
- Objective-C 執行時程式設計指南-介紹Object程式設計
- iOS資料埋點統計方案選型(附Demo):執行時Method Swizzling機制與AOP程式設計(面向切面程式設計)iOS程式設計
- JS定時器和單執行緒非同步特性JS定時器執行緒非同步
- rustlings v6.0 執行時出現 “ You are trying to run Rustlings using the old method before version 6”Rust
- 雲原生應用程式執行時 Kyma 的主要特性介紹
- iOS開發·runtime原理與實踐: 方法交換篇(Method Swizzling)(iOS“黑魔法”,埋點統計,禁止UI控制元件連續點選,防奔潰處理)iOSUI控制元件
- 巧用 LLVM 特性: Objective-C Class Properties 解耦LVMObject解耦
- JVM的特性,透過程式碼來揭秘執行時資料區JVM
- Java面試題:@PostConstruct、init-method和afterPropertiesSet執行順序?Java面試題Struct
- Java8 新特性併發篇(一) | 執行緒與執行器Java執行緒
- MySQL定時執行MySql
- Go高階特性 15 | 執行時反射:字串和結構體之間轉換Go反射字串結構體
- oracle查詢sql執行耗時、執行時間、sql_idOracleSQL
- 執行時框架,編譯時框架框架編譯
- 執行計劃沒變,執行時快時慢是怎麼回事?
- Java 21 新特性:虛擬執行緒(Virtual Threads)Java執行緒thread
- 聊聊JDK19特性之虛擬執行緒JDK執行緒
- Win10安全特性之執行流保護Win10
- 自適應查詢執行:在執行時提升Spark SQL執行效能SparkSQL
- 03—執行時資料區概述及執行緒執行緒
- Java執行時資料Java
- MySQL checkpoint執行時機MySql
- Redis 6.0 新特性-多執行緒連環13問!Redis執行緒
- 執行緒安全性保證---JMM特性詳解執行緒
- 自己動手實現springboot執行時執行java原始碼(執行時編譯、載入、註冊bean、呼叫)Spring BootJava原始碼編譯Bean
- JVM執行時資料區JVM
- yii執行phpunit時報錯PHP