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 SwizzlingObject
- Objective-C Method Swizzling 的最佳實踐Object
- Objective-C Runtime (三):Method Swizzling(方法替換)Object
- ios method swizzlingiOS
- [譯]理解 Objective-C 執行時Object
- Swift 5 之後 "Method Swizzling"?Swift
- 【iOS開發】扯淡 Method SwizzlingiOS
- Method Swizzling 和 AOP 實踐
- Objective-C的物件模型與執行時Object物件模型
- Swift3中的 Method SwizzlingSwift
- 關於 Method Swizzling 的一點思考
- Objective-C Runtime(四)isa swizzlingObject
- Objective-C 執行時程式設計指南-介紹Object程式設計
- Objective-C Runtime 執行時之六:拾遺Object
- 死磕Objective-C runtime執行時之一Object
- Objective-C 2.0 執行時系統程式設計Object程式設計
- iOS逆向之旅(進階篇) — HOOK(Method Swizzling)iOSHook
- iOS開發之 Method Swizzling 深入淺出iOS
- Objective-C Runtime 執行時之一:類與物件Object物件
- iOS 開發:『Runtime』詳解(二)Method SwizzlingiOS
- Runtime Method Swizzling開發例項彙總
- OC isa結構、訊息傳遞、Method Swizzling
- iOS資料埋點統計方案選型(附Demo):執行時Method Swizzling機制與AOP程式設計(面向切面程式設計)iOS程式設計
- Aspectj 實現Method條件執行
- Objective-C Runtime 執行時之三:方法與訊息Object
- ios runtime之Method Swizzling及其應用場景iOS
- Objective-C Runtime 執行時之五:協議與分類Object協議
- iOS Runtime簡單介紹,以及不同類的Method SwizzlingiOS
- Android新特性解析一:執行時許可權Android
- JS定時器和單執行緒非同步特性JS定時器執行緒非同步
- Objective-C Runtime 執行時之二:成員變數與屬性Object變數
- Objective-C Primer(3)Multiple arguments for one method, synthesize and dynamicObject
- objective-c 多執行緒注意的問題Object執行緒
- 雲原生應用程式執行時 Kyma 的主要特性介紹
- JVM的特性,透過程式碼來揭秘執行時資料區JVM
- java反射機制之Method invoke執行呼叫方法例子Java反射
- Objective-C 動態特性應用指南Object
- Objective-C 2015新特性Object