一: iOS开发常见的一个崩溃信息就是unrecognized selector sent to instance,这是因为调用了不存在的方法导致的(比如字典当做数组来用,使用了下标. 数组当做字典来用,调用了键值对取值),在所有的崩溃中占有相当大的比例
二: 还有一种占比比较高的闪退就是NULL,本来和后台说好的数据类型不会错,但是呢,总是不如意啊.尤其是php当没有数据的时候字典就变成数组了,这样的话客户端解析一不小心就JJ了
不过不怕,咱有黑科技,接下来请往下看
消息转发流程:
下面先来解决第一种情况
1,首先在ViewController类中创建对象以及调用person的sendMessage方法
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person*p = [[Person alloc] init];
[p1 performSelector:NSSelectorFromString(@"sendMessage") withObject:nil];
}
@interface Person : NSObject
@end
#import "Person.h"
@implementation Person
@end
Student类:
@interface Student :NSObject
@end
#import "Student.h"
@implementation Student
@end
当 Runtime 系统在 Cache 和类的方法列表(包括父类)中找不到要执行的方法时,Runtime 会调用 resolveInstanceMethod: 或 resolveClassMethod: 来给我们一次动态添加方法实现的机会。我们需要用 class_addMethod 函数完成向特定类添加特定方法实现的操作:
动态方法解析
在Person.m中实现一下方法就可以做到不闪退了.
但是这样做不通用,我们可以搞一个继承自NSObject的类,其他的类都继承与这个类,这样只需要在父类中实现以下方法就可以做到不闪退了.不可在分类中实现此方法,会报错的
v@:中, v表示返回值void, @表示对象self, :表示SEL
//在.m中实现这两个方法:
-(void)noObjMethod{
NSLog(@"未实现这个实例方法");
}
+(void)noClassMethod{
NSLog(@"未实现这个类方法");
}
//并且重写消息转发的方法:
// 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.
//注意:实例方法是存在于当前对象对应的类的方法列表中
+(BOOL)resolveInstanceMethod:(SEL)sel{
SEL aSel = NSSelectorFromString(@"noObjMethod");
Method aMethod = class_getInstanceMethod(self, aSel);
class_addMethod([self class], sel, method_getImplementation(aMethod), method_getTypeEncoding(method));
return YES;
}
重定向
2,如果resolveInstanceMethod 返回值为NO,会执行- (id)forwardingTargetForSelector:(SEL)aSelector,此方法会将消息转发给Student类实现
- (id)forwardingTargetForSelector:(SEL)aSelector {
if(aSelector ==@selector(sendMessage:)) {
return [Student new];
}else{
return [super forwardingTargetForSelector:aSelector];
}
}
转发
3,如果- (id)forwardingTargetForSelector:(SEL)aSelector返回值为nil,则会调用以下方法
- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector {
if(aSelector ==@selector(sendMessage:)) {
return [NSMethodSignature signatureWithObjCTypes:"V@:@"];
}
return nil;
}
- (void)forwardInvocation:(NSInvocation*)anInvocation {
SEL sel = [anInvocation selector];
Student *stu = [Student new];
if([stu respondsToSelector:sel]) {
[anInvocation invokeWithTarget:stu];
}else{
[super forwardInvocation:anInvocation];
}
}
注:- (void)forwardInvocation:(NSInvocation*)anInvocation 方法实现可以将消息转发给多个对象实现
如果以上三种重载都没执行消息,此时会调用- (void)doesNotRecognizeSelector:(SEL)aSelector方法,此时程序会崩溃
- (void)doesNotRecognizeSelector:(SEL)aSelector {
NSLog(@"doesNotRecognizeSelector");
}
从流程图可以看出,越是往后开销越大,所以在早期做出预防处理是最好的选择
所以直接在父类中重写resolveInstanceMethod方法,就可以做到程序不会崩溃了
接下来解决第二种情况,就是NULL的情况
这时候可以搞一个分类,如下:
#import <Foundation/Foundation.h>
@interface NSNull (Exception)
@end
#import "NSNull+Exception.h"
#import <objc/runtime.h>
@implementation NSNull (Exception)
#define pLog
#define JsonObjects @[@"",@0,@{},@[]]
//在.m中实现这两个方法:
-(void)noObjMethod{
NSLog(@"未实现这个实例方法");
}
+(void)noClassMethod{
NSLog(@"未实现这个类方法");
}
+(BOOL)resolveInstanceMethod:(SEL)sel{
for(id jsonObj in JsonObjects) {
if([jsonObj respondsToSelector:sel]) {
#ifdef pLog
NSLog(@"NULL出现啦!这个对象应该是是_%@",[jsonObj class]);
#endif
}
}
SEL aSel = NSSelectorFromString(@"noObjMethod");
Method aMethod = class_getInstanceMethod(self, aSel);
class_addMethod([self class], sel, method_getImplementation(aMethod), "v@:");
return YES;
}
如果此时在 ViewController.m中调用一下错误的字典 则不会引起崩溃
NSDictionary* dict = [[NSNull alloc] init];
[dict objectForKey:@"123"];
控制台会打印出以下信息:
2019-03-02 09:44:30.905674+0800 Test[28763:7642018] NULL出现啦!这个对象应该是是___NSDictionary0
2019-03-02 09:44:30.905790+0800 Test[28763:7642018] 未实现这个实例方法
现在就可以随意折腾了
好了,至此咱们的APP就可以减少大部分闪退问题了
Demo地址:GitHub - yeshibuzhong/iOS 中的Runtime_01
网友评论