前言:在程序發(fā)版之后雁歌,會偶發(fā)地出現(xiàn)消息找不到而導(dǎo)致的Crash(unrecognized selector sent to class ),最知名地后臺返回null對象柱查。我們可能判斷不嚴(yán)謹(jǐn)廓俭,當(dāng)做字典或數(shù)組處理,由于OC的動(dòng)態(tài)性唉工,就會發(fā)生錯(cuò)誤研乒。誠然,良好地代碼邏輯可以避免這些問題淋硝。但是對于已經(jīng)非我們自己寫地代碼雹熬,或者其他地SDK引起地問題,讓我們防不勝防谣膳。所以橄唬,在Release下添加一個(gè)方法找不地防護(hù)還有很有作用地,至少保證不Crash参歹,還可以把錯(cuò)誤統(tǒng)計(jì)仰楚,方便后續(xù)地維護(hù)!
OC是消息機(jī)制犬庇,方法調(diào)用就是消息發(fā)送僧界,這個(gè)流程不清楚的同學(xué)可以看看我前面的文章。當(dāng)一個(gè)消息找不到時(shí)候就會進(jìn)行消息轉(zhuǎn)發(fā)臭挽。這時(shí)捂襟,有三次拯救地機(jī)會。
- 首先調(diào)用 +(BOOL)resolveInstanceMethod:(SEL)sel或者+ (BOOL)resolveClassMethod:(SEL)sel
給我們一次動(dòng)態(tài)實(shí)現(xiàn)的機(jī)會,但是這個(gè)不合適欢峰,這樣會使類添加一個(gè)這個(gè)方法葬荷,我們也不知道方法地具體實(shí)現(xiàn) - 調(diào)用-(id)forwardingTargetForSelector:(SEL)aSelector
給我們一次轉(zhuǎn)發(fā)給其他對象,如果返回一個(gè)非nil.消息將會轉(zhuǎn)發(fā)給該對象.這個(gè)也不合適涨共,因?yàn)槲覀円膊恢酪l(fā)給誰處理 - 調(diào)用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法來獲取這個(gè)選擇子的方法簽名.然后在- (void)forwardInvocation:(NSInvocation *)anInvocation處理,這個(gè)就是我們想用的。就是要HOOK這兩個(gè)方法宠漩。
核心原理举反,利用Method-Swizzling達(dá)到HOOK這兩個(gè)動(dòng)態(tài)解析方法,為我所用扒吁。廢話不多說火鼻,上代碼,只所以寫在Load方法里面是因?yàn)閘oad方法會在啟動(dòng)之前自動(dòng)的調(diào)用雕崩。用dispatch_once防止有人手動(dòng)調(diào)用load方法魁索,防止再次交換就等于沒有交換。
+ (void)load {
static dispatch_once_t onceToken1;
dispatch_once(&onceToken1, ^{
Class class = [self class];
SEL originalSelector = @selector(forwardInvocation:);
SEL swizzledSelector = @selector(jessica_forwardInvocation:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
static dispatch_once_t onceToken2;
dispatch_once(&onceToken2, ^{
Class class = [self class];
SEL originalSelector = @selector(methodSignatureForSelector:);
SEL swizzledSelector = @selector(jessica_methodSignatureForSelector:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
接下來就是一個(gè)有錯(cuò)誤的實(shí)現(xiàn)方法盼铁,原理非常簡單粗蔚,jessica_methodSignatureForSelector一定不能返回nil。所以當(dāng)他解析不了地是時(shí)候強(qiáng)行給他一個(gè)NSMethodSignature饶火,之所以這個(gè)寫是因?yàn)樘O果地編碼規(guī)則鹏控。jessica_forwardInvocation是能響應(yīng)就去處理,響應(yīng)不了就不處理趁窃。代碼如下
- (void)jessica_forwardInvocation:(NSInvocation *)anInvocation {
if ([self respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:self];
}
}
- (NSMethodSignature *)jessica_methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *methodSignature = [[self class] instanceMethodSignatureForSelector:aSelector];
if (methodSignature == nil) {//這里是關(guān)鍵
methodSignature = [NSMethodSignature signatureWithObjCTypes:"@^v^c"];
}
return methodSignature;
}
高高興興地集成到代碼里面,但是還沒等這個(gè)發(fā)揮作用急前,每次彈起鍵盤就會crash醒陆。如果我們只看左面地調(diào)用棧因?yàn)槭荱IKit框架,我們啥也看到裆针,這時(shí)候可以用LLDB指令刨摩,輸入bt指令,這樣地調(diào)用棧才能解決問題世吨,發(fā)現(xiàn)問題出現(xiàn)在這個(gè)UIKeyboardInputManagerClient身上澡刹,這個(gè)類調(diào)用methodSignatureForSelector這個(gè)方法,然說得到是一個(gè)nil耘婚。我猜想是這個(gè)類也做了消息轉(zhuǎn)發(fā)罢浇,一開始我地想法非常簡單,我判斷一下這個(gè)類沐祷,是這個(gè)類我就不給他做解析了嚷闭,讓他還是調(diào)用原來地方法,果然可行赖临,但是這樣地方法并不好胞锰,因?yàn)檫@樣地類可能還會有,可能系統(tǒng)升級也會多兢榨。所以不能寫死嗅榕,這時(shí)候runtime又有用了顺饮,我可以利用runtime,去查詢一下該類是否重寫過methodSignatureForSelector這個(gè)方法凌那,如果重寫過我就給你不去處理了兼雄。方便大家集成,我就上完整代碼了案怯!
.h文件
#import <Foundation/Foundation.h>
@interface NSObject (JessicaMessageForwarding_h)
//是否重寫了 methodSignatureForSelector
@property (assign, nonatomic) BOOL isOverriMethodSignatureForSelector;
//是否重寫了forwardInvocation
@property (assign, nonatomic) BOOL isOverriForwardInvocation;
@end
.m文件
#import "NSObject+JessicaMessageForwarding_h.h"
#import <objc/runtime.h>
@implementation NSObject (JessicaMessageForwarding_h)
+ (void)load {
static dispatch_once_t onceToken1;
dispatch_once(&onceToken1, ^{
Class class = [self class];
SEL originalSelector = @selector(forwardInvocation:);
SEL swizzledSelector = @selector(jessica_forwardInvocation:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
static dispatch_once_t onceToken2;
dispatch_once(&onceToken2, ^{
Class class = [self class];
SEL originalSelector = @selector(methodSignatureForSelector:);
SEL swizzledSelector = @selector(jessica_methodSignatureForSelector:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)jessica_forwardInvocation:(NSInvocation *)anInvocation {
if (self.isOverriForwardInvocation) {
return [self jessica_forwardInvocation:anInvocation];
}
if ([self respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:self];
}
}
- (NSMethodSignature *)jessica_methodSignatureForSelector:(SEL)aSelector {
if (self.isOverriMethodSignatureForSelector) {
return [self jessica_methodSignatureForSelector:aSelector];
}
NSMethodSignature *methodSignature = [[self class] instanceMethodSignatureForSelector:aSelector];
if (methodSignature == nil) {
#warning 諸如UIKeyboardInputManagerClient 這個(gè)類自己重寫了 methodSignatureForSelector方法, 就得遵循自己地方法
self.isOverriMethodSignatureForSelector = NO;
self.isOverriForwardInvocation = NO;
for (NSString *methodStr in [self getAllMethodArray]) {
if ([methodStr isEqualToString:@"methodSignatureForSelector:"]) {
self.isOverriMethodSignatureForSelector = YES;
}
if ([methodStr isEqualToString:@"forwardInvocation:"]) {
self.isOverriForwardInvocation = YES;
}
}
if (self.isOverriMethodSignatureForSelector) {
return [self jessica_methodSignatureForSelector:aSelector];
}
methodSignature = [NSMethodSignature signatureWithObjCTypes:"@^v^c"];
}
return methodSignature;
}
-(NSArray *)getAllMethodArray{
u_int count;
NSMutableArray *arrayM = [NSMutableArray array];
Method *mothList_f = class_copyMethodList([self class],&count) ;
for (int i = 0; i < count; i++) {
Method temp_f = mothList_f[i];
SEL name_f = method_getName(temp_f);
const char * name_s = sel_getName(name_f);
[arrayM addObject:[NSString stringWithUTF8String:name_s]];
}
free(mothList_f);
return arrayM.copy;
}
-(BOOL)isOverriMethodSignatureForSelector{
NSNumber* vale = objc_getAssociatedObject(self, @selector(isOverriMethodSignatureForSelector));
return [vale boolValue];
}
-(BOOL)isOverriForwardInvocation{
NSNumber* vale = objc_getAssociatedObject(self, @selector(isOverriForwardInvocation));
return [vale boolValue];
}
-(void)setIsOverriMethodSignatureForSelector:(BOOL)vale{
objc_setAssociatedObject(self, @selector(isOverriMethodSignatureForSelector), [NSNumber numberWithBool:vale], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(void)setIsOverriForwardInvocation:(BOOL)vale{
objc_setAssociatedObject(self, @selector(isOverriForwardInvocation), [NSNumber numberWithBool:vale], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
建議大家集成之前多測試幾個(gè)頁面君旦,沒問題最好只在release下生效,如果在您地代碼里面不兼容嘲碱,發(fā)生異常金砍。請與我聯(lián)系,我會盡我所能地去完善麦锯。如果涉及到runime不懂地地方可以看看我原來地帖子恕稠,也可以與我交流,感謝您地閱讀扶欣。
補(bǔ)充:之前我說的可以錯(cuò)誤統(tǒng)計(jì)鹅巍,就是在 methodSignature = [NSMethodSignature signatureWithObjCTypes:"@vc"]; 這個(gè)代碼地時(shí)候添加一些保存上傳地邏輯,把類名料祠,方法名骆捧,當(dāng)時(shí)地調(diào)用堆棧上傳就好。