本文將介紹oc中的方法攔截器的實現(xiàn)思路,如何攔截一個方法福贞,并在執(zhí)行方法前進行一些前置業(yè)務邏輯后決定是否繼續(xù)執(zhí)行方法。
緣由:最近公司需要將組件進行授權管理(類似第三方SDK的授權),之前編寫過很多內部使用的SDK乍钻,現(xiàn)在需要將這些SDK通過授權管理起來咖杂,我的想法是編寫一個統(tǒng)一授權管理的模塊庆寺,需要被管理的SDK引用這個授權管理模塊即可,要求是對原SDK盡量少做代碼改動诉字。
在百度搜了一圈都沒有想要的答案懦尝,所以決定自己提需求自己實現(xiàn)。
需求:
1.對需要被攔截監(jiān)聽的方法無代碼侵入壤圃。
2.成功攔截需要被監(jiān)聽的方法陵霉,并在方法執(zhí)行前做前置邏輯判斷,最后決定是否執(zhí)行被攔截的方法伍绳。
實現(xiàn)思路:
這么刁鉆的需求肯定用runtime了踊挠,思路就是將被監(jiān)聽的方法的實例利用方法交換替換成自定義的方法,在自定義方法中執(zhí)行我們自己的邏輯判斷墨叛,最后決定是否需要繼續(xù)原方法調用止毕。(是不是很簡單模蜡?上才藝!)
這里只講實例方法的攔截扁凛,因為任何類都可能需要注冊自己需要被監(jiān)聽的方法忍疾,所以創(chuàng)建一個NSObject的分類
NSObject+SRModuleAuthorization.h:
@interface NSObject (SRModuleAuthorization)
- (void)registerNeedMonitorSEL:(SEL)sel;
@end
頭文件只對外暴露一個注冊需要被監(jiān)聽方法的API。
下面來看實現(xiàn)NSObject+SRModuleAuthorization.m:
//注冊需要被攔截的方法
- (void)registerNeedMonitorSEL:(SEL)sel {
//獲取方法的名稱谨朝,根據(jù)該名稱設置自定義的方法名稱
NSString *methodName = NSStringFromSelector(sel);
NSString *newMethodName = [NSString stringWithFormat:@"sr_auth_%@", methodName];
//獲取自定義方法的實例
SEL newSel = NSSelectorFromString(newMethodName);
Method newMethod = class_getInstanceMethod([self class], newSel);
//判斷是否已經添加了自定義的方法卤妒,如果為空就添加自定義方法
if (!newMethod) {
//獲取被攔截方法的結構體對象
Method method = class_getInstanceMethod(self.class, sel);
//獲取被攔截方法的方法簽名字符串
const char *functionType = method_getTypeEncoding(method);
NSString *functionTypeStr = [NSString stringWithUTF8String:functionType];
//判斷方法簽名的字符串中,:8是否是最后兩個字符字币,
//如果是則代表無參數(shù)方法则披,否則是有參方法,
//針對這兩種方法添加不同的自定義攔截方法洗出。
NSRange range = [functionTypeStr rangeOfString:@":8"];
Method intercetionMethod;
if (range.location + range.length < functionTypeStr.length) {//有參的添加有參攔截方法
intercetionMethod = class_getInstanceMethod(self.class, @selector(interception:));
} else {//添加無參攔截方法
intercetionMethod = class_getInstanceMethod(self.class, @selector(interception));
}
//獲取自定義攔截方法的實例士复,
//并將自定義方法的selector與實例關聯(lián),動態(tài)地添加進當前類型翩活。
IMP imp = method_getImplementation(intercetionMethod);
class_addMethod([self class], newSel, imp, functionType);
//最后重新獲取自定義方法的方法結構體阱洪,
//并與被監(jiān)聽方法進行實例交換,交換后菠镇,
//被監(jiān)聽的方法被調用時冗荸,就會執(zhí)行我們自定義的方法實例,即interception
Method method2 = class_getInstanceMethod(self.class, newSel);
method_exchangeImplementations(method, method2);
}
}
下面是interception方法的實現(xiàn)利耍,flag就理解為一個開關就行蚌本,interception的主要功能是決定是否需要執(zhí)行被攔截的方法。
//無參攔截方法
- (void)interception {
if (flag) {
NSString *methodName = NSStringFromSelector(_cmd);
NSString *newMethodName = [NSString stringWithFormat:@"dl_auth_%@", methodName];
SEL newSel = NSSelectorFromString(newMethodName);
NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:newSel];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = self;
invocation.selector = newSel;
[invocation invoke];
} else {
//執(zhí)行自定義代碼
}
}
有參攔截方法的參數(shù)是一個可變參數(shù)列表隘梨,我們通過遍歷并設置invocation對象的參數(shù)已達到傳參的目的程癌。
注意:這里有個問題,被監(jiān)聽的方法中如果參數(shù)類型不是對象類型的話會報錯出嘹,所以本文的方法攔截只適用于方法簽名中參數(shù)都是對象類型的方法席楚。(目前還沒有找到能夠解決的辦法,如果你讀到這里有更好的解決方案税稼,希望你能告訴我)烦秩。
//有參攔截方法
- (void)interception:(id)param, ... {
if (flag) {
NSString *methodName = NSStringFromSelector(_cmd);
NSString *newMethodName = [NSString stringWithFormat:@"dl_auth_%@", methodName];
SEL newSel = NSSelectorFromString(newMethodName);
NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:newSel];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = self;
invocation.selector = newSel;
NSInteger argCount = signature.numberOfArguments;
va_list args;
va_start(args, param);
NSMutableArray *params = [NSMutableArray array];
if (argCount > 2) {
[params addObject:param];
}
for (int i = 3; i < argCount; i++) {
param = va_arg(args, id);
[params addObject:param];
}
va_end(args);
for (int i = 0; i < params.count; i++) {
id obj = params[i];
[invocation setArgument:&obj atIndex:i + 2];
}
[invocation invoke];
} else {
//執(zhí)行自定義代碼
}
}
以上就是我的統(tǒng)一授權組件的實現(xiàn)代碼,使用的時候就很方便了郎仆,只需要在要被監(jiān)聽的類的初始化方法中只祠,調用self的registerNeedMonitorSEL方法就行了,需要監(jiān)聽那個方法就監(jiān)聽哪個扰肌。
示例
[self registerNeedMonitorSEL:@selector(test)];
你也可以編寫一個監(jiān)聽所有方法的方法:
注意:如果方法簽名中有非對象的入?yún)t會報錯抛寝。
- (void)registerNeedMonitorAllMethod {
unsigned int count;
Method *methodList = class_copyMethodList([self class], &count);
for (int i = 0; i < count; i++) {
Method method = methodList[i];
SEL selector = method_getName(method);
[self registerNeedMonitorSEL:selector];
}
}
//調用時一句代碼搞定
[self registerNeedMonitorAllMethod];