Method-Swizzle
Method Swizziling 是OC運行時給我們的用于交換Method的實現(xiàn)方式(IMP)
的能力。
其用到的核心方法就是method_exchangeImplementations
,代碼存在于objc-runtime-new.mm
void method_exchangeImplementations(Method m1, Method m2)
{
if (!m1 || !m2) return;
mutex_locker_t lock(runtimeLock);
IMP m1_imp = m1->imp;
m1->imp = m2->imp;
m2->imp = m1_imp;
// RR/AWZ updates are slow because class is unknown
// Cache updates are slow because class is unknown
// fixme build list of classes whose Methods are known externally?
flushCaches(nil);
adjustCustomFlagsForMethodChange(nil, m1);
adjustCustomFlagsForMethodChange(nil, m2);
}
原理實現(xiàn)圖
經(jīng)過上述我們就知道蛤售,當經(jīng)過
Method-Swizzling
后原本兩個方法的實現(xiàn)IMP
得到了互換誓焦。
具體使用
我們準備連個文件.h
和兩個.m
文件;
LGPerson
表示如下
#import <Foundation/Foundation.h>
@interface LGPerson : NSObject
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end
LGPerson.m
表示如下
@implementation LGPerson
- (void)personInstanceMethod{
NSLog(@"person對象方法:%s",__func__);
}
- (void)personClassMethod{
NSLog(@"person類方法:%s",__func__);
}
@end
LGPerson+LG.h
表示如下
#import "LGPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson (LG)
@end
NS_ASSUME_NONNULL_END
LGPerson+LG.m
表示如下
#import "LGPerson+LG.h"
#import <objc/runtime.h>
@implementation LGPerson (LG)
+(void)initialize{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL defaultSelector = @selector(personInstanceMethod);
SEL swizzledSelector = @selector(MonitoringCallPersonInstanceMethod);
Method defaultMethod = class_getInstanceMethod(class, defaultSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
method_exchangeImplementations(defaultMethod, swizzledMethod);
});
}
-(void)MonitoringCallPersonInstanceMethod{
NSLog(@"現(xiàn)在是MonitoringCallPersonInstanceMethod被調(diào)用了哦");
}
@end
當我們調(diào)用
LGPerson *p = [[LGPerson alloc] init];
[p personInstanceMethod];
輸出了
現(xiàn)在是MonitoringCallPersonInstanceMethod被調(diào)用了哦
是不是現(xiàn)在有了一個大概的認識
method-swizzling 使用時注意點
- 1诺祸、使用過程中保證一次性,為什么要保持一次呢携悯?如果方法交換兩次的時候不就交換回來了嗎,所以避免重復交換可通過
dispatch_once
來實現(xiàn),大家可以從我的LGPerson+LG.m
例子中看出
我這里沒有將方法添加到
+load
筷笨,是因為保持OC對象的懶加載特性憔鬼,讓我們的應用啟動變快
- 2龟劲、子類實現(xiàn)的方法交換的時候,被交換的方法來自父類;如果這樣就會出問題轴或;要保證交換的方法是來自當前本類
- 2.1當這種情況發(fā)生后昌跌,父類的其他子類也會調(diào)用實現(xiàn)method-swizzling的方法,這種侵入性太大了照雁。
- 2.2 當子類中維持父類方法的調(diào)用我們會這樣寫(LGStudent是LGPerson的子類)
@implementation LGStudent (LG)
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL defaultSelector = @selector(personInstanceMethod);
SEL swizzledSelector = @selector(lg_studentInstanceMethod);
Method defaultMethod = class_getInstanceMethod(class, defaultSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
method_exchangeImplementations(defaultMethod, swizzledMethod);
});
}
- (void)lg_studentInstanceMethod{
[self lg_studentInstanceMethod]; //lg_studentInstanceMethod -/-> personInstanceMethod
NSLog(@"LGStudent分類添加的lg對象方法:%s",__func__);
}
@end
那么當父類或者父類的其他子類調(diào)用就會出現(xiàn)
為了避免這個問題蚕愤,一般方式是給類添加方法
class_addMethod
- 如果添加成功,即當前類是沒有這個方法的,可以通過
class_replaceMethod
進行替換饺蚊,這樣就不會修改父類和其他子類
-如果添加失敗,即當前類是存在該方法的萍诱,則可以通過method_exchangeImplementations
進行方法交換
一個比較合理的方法交換實現(xiàn)
+ (void)lg_betterMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
if (!cls) NSLog(@"傳入的交換類不能為空");
// oriSEL personInstanceMethod
// swizzledSEL lg_studentInstanceMethod
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
// 嘗試添加你要交換的方法 - lg_studentInstanceMethod
BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
/**
personInstanceMethod(sel) - lg_studentInstanceMethod(imp)
lg_studentInstanceMethod (swizzledSEL) - personInstanceMethod(imp)
*/
if (success) {// 自己沒有 - 交換 - 沒有父類進行處理 (重寫一個)
class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else{ // 自己有
method_exchangeImplementations(oriMethod, swiMethod);
}
}
Method-Swizzling 使用場景(黑魔法確實是黑魔法)
1、在逆向中可以對方法下hook
2污呼、監(jiān)聽方法的調(diào)用
3裕坊、面向切換編程AOP(Aspect Oriented Programming),在某個方法調(diào)用前,檢測參數(shù)是否合理燕酷,不合理做一些防崩潰處理;常見的是[NSArray objectIndex:]
籍凝,檢測數(shù)組是否越界,如果越界則直接返回nil悟狱,如果沒有越界静浴,則正常調(diào)用