標簽(空格分隔): iOS
NSObjectMethodHook是業余自己實現,可以防止 unrecognized selector sent to instance 崩潰的Demo
實現原理,在iOS消息轉發鏈上做處理。
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self _hookMethod:[NSObject class]
OriginSelector:@selector(forwardingTargetForSelector:)
SwizzledSelector:@selector(v_forwardingTargetForSelector:)];
});
}
在load方法里,交換NSObject的forwardingTargetForSelector 方法。如果方法調用走到這一步,判斷一下有沒有實現往下一步的消息轉發流程。如果沒有實現,就動態生成一個類,并且給動態生成的類添加同樣的方法簽名,然后生成對象,返回。
這里判斷有沒有自己實現消息轉發流程的具體方法是,判斷對應的函數簽名地址和NSObject是不是相同,詳情見代碼注視,
交換方法如下:
+ (void)_hookMethod:(Class)cls OriginSelector:(SEL)originalSelector SwizzledSelector:(SEL)swizzledSelector {
//替換某各類的方法
Method originalMethod = class_getInstanceMethod(cls, originalSelector);
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
BOOL didAddMethod =
class_addMethod(cls,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(cls,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
///具體實現如下
- (id)v_forwardingTargetForSelector:(SEL)sel1 {
SEL sel = NSSelectorFromString(@"forwardingTargetForSelector:");
Method method = class_getInstanceMethod(NSClassFromString(@"NSObject"), sel);
Method _m = class_getInstanceMethod([self class],sel);
/// 類本身有沒有實現消息轉發流程
BOOL transmit = method_getImplementation(_m) == method_getImplementation(method);
/// 有木有實現下一步消息轉發流程
if (transmit) {
/// 判斷有沒有實現
SEL sel1 = NSSelectorFromString(@"methodSignatureForSelector:");
Method method1 = class_getInstanceMethod(NSClassFromString(@"NSObject"), sel1);
Method _m1 = class_getInstanceMethod([self class],sel1);
transmit = method_getImplementation(_m1) == method_getImplementation(method1);
}
if (transmit) { /// 創建一個新類
NSString *errClassName = NSStringFromClass([self class]);
NSString *errSel = NSStringFromSelector(sel1);
NSLog(@"出問題的類,出問題的方法 == %@ %@",errClassName, errSel);
NSString *className = @"PandaClass";
Class cls = NSClassFromString(className);
/// 如果類不存在 動態創建一個類
if (!cls) {
Class superCls = [NSObject class];
cls = objc_allocateClassPair(superCls, className.UTF8String, 0);
/// 給類添加方法
class_addMethod(cls, sel1, (IMP)pandaTVCC, "@@:@");
objc_registerClassPair(cls);
}
/// 如果類沒有對應的方法,則動態添加一個
if (!class_getInstanceMethod(NSClassFromString(className), sel1)) {
class_addMethod(cls, sel1, (IMP)pandaTVCC, "@@:@");
}
/// 把消息轉發到當前動態生成類的實力上
return [[NSClassFromString(className) alloc] init];
}
/// 類本身實現了消息轉發流程
/// 走正常的消息轉發流程
return [self v_forwardingTargetForSelector:sel1];
}
踩坑:1:系統有好多私有類同樣hook 了 forwardingTargetForSelector: 要處理
2:一些類有可能自己實現了消息轉發要判斷。
最開始通過 有沒有 類是不是以“”下劃線開頭, 判斷是不是系統class 然后特殊的類做特殊的處理。后來發現有些系統的類也不是
實現過程中發現上面 1 2 屬于同一個問題。所以只要判斷這個類有沒有實現消息轉發流程就好了,
1:類有沒有實現 forwardingTargetForSelector:函數,
2:類有沒有實現methodSignatureForSelector 函數,實現了就表明類實現了消息轉發流程,那么不處理這個類