什么时候会报 unrecognized selector 的异常?
函数 objc_msgSend()的消息传递失败了,消息转发也失败,就会抛出这个异常。
首先在类对象或者元类对象中找不到 selector,去父类找也找不到
Runtime 的三次拯救机会也放弃:
动态方法决议Dynamic Method Resolution:
在 resolveInstanceMethod 或者 resolveClassMethod 中其实是可以给一个类对象添加 Method 的。
这个方法决议阶段能用来做什么我不知道,但是肯定不能用来做「crash 防护」。原因是:
respondsToSelector 会调用这个阶段的两个函数,而我们在这两个函数中给这个类添加方法(而且也不是真正的实现,是日志上报的实现)的话,就会对调用方造成误解:我问你你能不能做,你说能做。结果发现你说的能做是「能上报给其他人说你做不了」,你只是避免了我问你的时候的崩溃,但是没有改变你不能做的事实。这不合理啊兄弟.
所以「crash 防护」更合理的做法是放在「快速转发 fast forwarding」阶段实现。
+ (BOOL)resolveClassMethod:(SEL)sel {
// 如果 dynamicClassMethod 实现了,那么不会跳到这里
if(sel == @selector(dynamicClassMethod)) {
Class class = objc_getMetaClass("MessageSendingDemo");
IMP imp = class_getMethodImplementation(class, @selector(UploadMissingFunc));
Method method = class_getInstanceMethod(class, @selector(UploadMissingFunc));
const char *type = method_getTypeEncoding(method);
return class_addMethod(class, sel, imp, type);
}
return [super resolveClassMethod:sel];
}
+ (void)UploadMissingFunc {
NSLog(@"就把未实现的方法换成了我了: %@", __func__,);
}
Fast Forwarding 快速转发
在 forwardingTargetForSelector: 中返回其他对象
使用场景:安全气囊,在函数 forwardingTargetForSelector 函数中添加 crash 上报逻辑,同时给这个未实现的 selector 创建一个空类(创建一个空对象)和空实现
- (id)forwardingTargetForSelector:(SEL)aSelector {
id forwardTarget = [super forwardingTargetForSelector:aSelector];
if (forwardTarget) {
return forwardTarget;
}
Class someClass = [self qiResponedClassForSelector:aSelector];
if (someClass) {
forwardTarget = [someClass new];
}
// 另请高明
return forwardTarget;
}
unrecognized selector崩溃防护
代码来自:https://github.com/ParsifalC/NetEaseBaymaxDemo/blob/master/NetEaseBaymaxDemo/NSObject%2BBaymax.m
MARK: Unrecognize Selector Protected
- (id)baymax_forwardingTargetForSelector:(SEL)aSelector {
// Ignore class which has overrided forwardInvocation method and System classes
if ([self isMethodOverride:[self class] selector:@selector(forwardInvocation:)] ||
![NSObject isMainBundleClass:[self class]] ||
[self isKindOfClass:[CPZombieObject class]]) {
return [self baymax_forwardingTargetForSelector:aSelector];
}
// 上报 crash 日志
NSLog(@"catch unrecognize selector crash %@ %@", self, NSStringFromSelector(aSelector));
NSLog(@"%@", [NSThread callStackSymbols]);
// 直接创建一个空类和空实现
Class baymaxProtector = [NSObject addMethodToStubClass:aSelector];
if (!self.baymax) {
self.baymax = [baymaxProtector new];
}
// 就这么被防护掉了
return self.baymax;
}
Normal Forwarding
在 methodSignatureForSelector: 函数中坚持不返回一个 NSMethodSignature,并且不在 forwardInvocation函数中把这个NSInvocation转发出去
快速转发只能转给一个对象,但是 Normal 转发可以转给很多个对象
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(work)) {
// 要执行 forwardInvocation ,必须返回函数签名
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
SEL aSelector = [anInvocation selector];
if ([[MyClass alloc] respondsToSelector:aSelector])
// 交给 MyClass 这个类去执行了
[anInvocation invokeWithTarget:[MyClass alloc]];
else
[super forwardInvocation:anInvocation];
}
这时你就获得了一个unrecognized selector 的异常
NSInvocation:用于在对象之间存储和转发消息,它包含了selector,target,参数和返回值,只需要给它一个正确的 target 对象,就能顺利执行一个函数(由target 对象执行的)。它可以被重复的分配到不同的 target。
NSMethodSignature:方法签名,主要是包含方法的返回值、参数的类型等信息