正常情况下App的闪退大部分是由数组越界;字典插入空值;字符串截取越界;定时器nstimer;kvo;野指针,僵尸对象等导致的闪退。一般在测试时正常如果后台返回不正常数据,前端没做防护的情况下很容易闪退。通过Runtime 可以在数据错误交换方法或者return避免闪退。但是runtime不可乱用,特别是多人开发时,如果不注意交换同一个方法就会出现问题。这个一般在上线打开,调试关闭。只能避免闪退不能避免bug。还是需要在代码中多做判断,防止越界,nil插入等情况!
以前也研究过一些关于runtime防护的相关问题都不够全面。最近在看博客时看到几篇很不错的博客和一些总结很不错的demo.
Runtime 在项目中的常见使用:
1 字典转模型;
2 动态添加交换拦截方法;防止闪退,换肤,统一设置字体颜色或者全局某个方法拦截
3 热更新;用到的依然是runtime 里面的转链接 forwardingTargetForSelector
4 获取属性列表;
5 归档解档;
6 动态添加属性 ;分类不可添加属性 通过runtime可以实现
7 实现万能跳转; 这个在收到消息时跳到具体页面通过类名生成相应对象
Method class_getClassMethod(Class cls, SEL name) cls:需要交换的类方法的所属类 class SEL:该类方法获取一个类的类方法的地址
Method class_getInstanceMethod(Class cls, SEL name) cls:需要交换的类实例方法的所属类 class SEL:该类的实例方法获取一个类的实例方法地址
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) cls:被添加方法的类名 name:方法名 imp:实现这个方法的函数 types:一个定义该函数返回值类型和参数类型的字符串动态的给一个类添加一个方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) 参数如class_addMethod类似该函数可以在运行时动态替换某个类的函数实现
void method_exchangeImplementations(Method m1, Method m2)交换两个方法的地址
runtime 在防护方面最核心的是通过method_exchangeImplementations交换IMP从而交换方法。就是当你掉用的方法出现错误时掉用自己的方法或者return从而防止Crash 同时上传闪退的记录便于修复。
一班都会在分类中添加方法例如NSarray:
1 新建分类
2 #import <objc/runtime.h> 引进框架
3 load方法里写交换方法 ,未什么放在load里可以搜(main函数前发生了什么)至于为什么要用dispatch_once 方法交换应该被保证,在程序中只会执行一次
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class clsI = NSClassFromString(@"__NSArrayI");
Method method1 = class_getInstanceMethod(clsI, @selector(objectAtIndexedSubscript:));
Method method2 = class_getInstanceMethod(clsI, @selector(yye_objectAtIndexedSubscript:));
method_exchangeImplementations(method1, method2);
Method method3 = class_getInstanceMethod(clsI, @selector(objectAtIndex:));
Method method4 = class_getInstanceMethod(clsI, @selector(yye_objectAtIndex:));
method_exchangeImplementations(method3, method4);
Class clsSingleI = NSClassFromString(@"__NSSingleObjectArrayI");
Method method5 = class_getInstanceMethod(clsSingleI, @selector(objectAtIndex:));
Method method6 = class_getInstanceMethod(clsSingleI, @selector(yyeSingle_objectAtIndex:));
method_exchangeImplementations(method5, method6);
});
}
- (id)yye_objectAtIndexedSubscript:(NSUInteger)index{
if(index>=self.count) return self.lastObject;
return [self yye_objectAtIndexedSubscript:index];
}
- (id)yye_objectAtIndex:(NSUInteger)index{
if(index>=self.count) return self.lastObject;
return [self yye_objectAtIndex:index];
}
- (id)yyeSingle_objectAtIndex:(NSUInteger)index{
if(index>=self.count){
return self.lastObject;
}
return [self yyeSingle_objectAtIndex:index];
}
1: + (void)load与+ (void)initialize的区别:+ (void)load:当类加载进内存的时候调用,而且不管有没有子类,都只会调用一次,在main函数之前调用,用途:1:可以新建类在该类中实现一些配置信息 2:runtime交换方法的时候,因为只需要交换一次方法,所有可以在该方法中实现交换方法的代码,用于只实现一次的代码
2:+ (void)initialize:当类被初始化的时候调用,可能会被调用多次,若是没有子类,则只会调用一次,若是有子类的话,该方法会被调用多次,若是子类的继承关系,先会调用父类的+ (void)initialize方法,然后再去调用子类的+ (void)initialize方法(若是继承关系,调用某个方法的时候,先会去父类中查找,若是父类中没有方法的实现就去子类中查找) 用途:1:在设置导航栏的全局背景的时候,只需要设置一次,可以重写该方法设置,最好是在该方法判断子类,若是自己,则实现设置全局导航栏的方法,若不是自己则跳过实现。2:在创建数据库代码的时候,可以在该方法中去创建,保证只初始化一次数据库实例,也可以用dispatch或是懒加载的方法中初始化数据库实例,也能保证只初始化一次数据库实例。其中也可以在+ (void)initialize方法中用dispatch也能保证即使有子类也只会初始化一次
2:交换方法:1:获取某个类的方法:class_getClassMethod:第一个参数:获取哪个类的方法 第二个参数:SEL:获取哪个方法
Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));
// 交互方法:runtime
method_exchangeImplementations(imageNamedMethod, xmg_imageNamedMethod);
也就是外部调用xmg_imageNamed就相当于调用了imageNamed,调用imageNamed就相当于调用了xmg_imageNamed
3:在分类中,最好不要重写系统方法,一旦重写,把系统方法实现给干掉,因为分类不是继承父类,而是继承NSObject,super没有改类的方法,所以就直接覆盖掉了父类的行为
关于博客网上很多都大同小异 创建分类 交换方法 博客;
关于demo这个还是总结的比较好:demo 这个demo 里面比较全但是需要经过关闭提示 ;调试时关闭上线打开。WKCrashManagerDemo这个demo 使用时注意几个warning的地方最好自己整理里整理。上面文章总结不够全面想到再继续补充!