# Foggy
### [Crash防护](https://juejin.cn/post/6911580676559110151)
# 前言
- **Crash分为两种,一种是由EXC_BAD_ACCESS引起的,原因是访问了不属于本进程的内存地址,有可能是访问已被释放的内存;另一种是未被捕获的Objective-C异常(NSException),导致程序向自身发送了SIGABRT信号而崩溃**
- **本文主要介绍APP运行时第二种Crash自动防护功能,暂时涵盖容器,字符串,没找到对应的函数,后台返回NSNull导致的崩溃,计时器,kvo,不在主线程中更新UI等崩溃**
- **有空再来慢慢补充其他类型**
#### Demo地址:[Foggy](https://github.com/yangKJ/Foggy)
### 设计原理
利用 Objective-C 语言的动态特性,采用AOP面向切面编程的设计思想,交换方法然后拦截处理崩溃信息
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/529e7eb738644bf2a32ddeb341a9e277~tplv-k3u1fbpfcp-watermark.image)
| 方法 | 功能 |
| :--- | :--- |
| class_getInstanceMethod | 获取实例方法 |
| class_getClassMethod | 获取类方法 |
| method_getImplementation | 获取一个方法的实现 |
| method_setImplementation | 设置一个方法的实现 |
| method_getTypeEncoding | 获取方法实现的编码类型 |
| class_addMethod | 添加方法实现 |
| class_replaceMethod | 用一个方法的实现,替换另一个方法的实现,即aIMP 指向 bIMP,但是bIMP不一定指向aIMP |
| method_exchangeImplementations | 交换两个方法的实现,即 aIMP -> bIMP, bIMP -> aIMP |
#### 交换实例方法
```
void kExceptionMethodSwizzling(Class clazz, SEL original, SEL swizzled){
Method originalMethod = class_getInstanceMethod(clazz, original);
Method swizzledMethod = class_getInstanceMethod(clazz, swizzled);
if (class_addMethod(clazz, original, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
class_replaceMethod(clazz, swizzled, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else{
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
```
#### 交换类方法
```
void kExceptionClassMethodSwizzling(Class clazz, SEL original, SEL swizzled){
Method originalMethod = class_getClassMethod(clazz, original);
Method swizzledMethod = class_getClassMethod(clazz, swizzled);
Class metaclass = objc_getMetaClass(NSStringFromClass(clazz).UTF8String);
if (class_addMethod(metaclass, original, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
class_replaceMethod(metaclass, swizzled, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else{
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
```
## 常见Crash和解决方案
### 一、没找到对应的函数 - Unrecognized Selector Sent to Instance
类型 | 原因
:---: | :---
SEL | unrecognized selector sent to instance .h定义但.m没实现
SEL | performSelector: 调用不存在的方法
SEL | delegate 回调前没有判空而是直接调用
SEL | id 类型没有判断类型,强行调用真实类型不存在的方法
SEL | copy 修饰的可变的字符串 \ 字典 \ 数组 \ 集合 \ Data,调用可变的方法
> 消息转发流程图:
> ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/25f3694cd3aa45ce8f2b90f3ab176cbb~tplv-k3u1fbpfcp-zoom-1.image)
#### 解决方案:
- 交换方法`methodSignatureForSelector:` 和 `forwardInvocation:`
- 对象调用方法经过三个阶段
1. 消息发送:查询cache和方法列表,找到了直接调用,找不到方法会进入下个阶段
2. 动态解析: 调用实例方法`resolveInstanceMethod`或类方法`resolveClassMethod`里面可以有一次动态添加方法的机会
3. 消息转发:首先会判断是否有其他对象可以处理方法`forwardingTargetForSelector`返回一个新的对象,如果没有新的对象进行处理,会调用`methodSignatureForSelector`方法返回方法签名,然后调用`forwardInvocation`
- 选择在消息转发的最后一步来做处理,`methodSignatureForSelector:`消息获得函数的参数和返回值,然后`[self respondsToSelector:aSelector]`判断是否有该方法,如果没有返回函数签名,创建一个NSInvocation对象并发送给`forwardInvocation`
### 二、容器越界 - 数组和字典
类型 | 原因
:---: | :---
NSArray | 数组索引越界、插入空对象
NSDictionary | key、value 为空
> 备注:可变的都继承自不可变的,所有可变的分类中,重复的方法就不用再次替换
#### 解决方案:
- 交换方法,然后防护处理,简单举个例子,NSArray 是一个类簇,它真正的类型是`__NSArrayI`,交换方法如下
```
Class __NSArrayI = objc_getClass("__NSArrayI");
/// 越界崩溃方式一:[array objectAtIndex:0];
kExceptionMethodSwizzling(__NSArrayI, @selector(objectAtIndex:), @selector(kj_objectAtIndex:));
/// 越界崩溃方式二:array[0];
kExceptionMethodSwizzling(__NSArrayI, @selector(objectAtIndexedSubscript:), @selector(kj_objectAtIndexedSubscript:));
```
交换后的处理
```
- (instancetype)kj_objectAtIndex:(NSUInteger)index{
NSArray *temp = nil;
@try {
temp = [self kj_objectAtIndex:index];
}@catch (NSException *exception) {
NSString *string = @"🍉🍉 crash:";
if (self.count == 0) {
string = [string stringByAppendingString:@"数组个数为零"];
}else if (self.count <= index) {
string = [string stringByAppendingString:@"数组索引越界"];
}
[KJCrashManager kj_crashDealWithException:exception CrashTitle:string];
}@finally {
return temp;
}
}
```
### 三、KVO
类型 | 原因
:---: | :---
KVO | 添加了监听,没有移除
#### 解决方案:
- 交换`removeObserver:forKeyPath:`方法,
```
- (void)kj_removeObserver:(NSObject*)observer forKeyPath:(NSString *)keyPath{
@try {
[self kj_removeObserver:observer forKeyPath:keyPath];
}@catch (NSException *exception) {
NSString *string = @"🍉🍉 crash:添加观察者后没有移除观察者导致";
[KJCrashManager kj_crashDealWithException:exception CrashTitle:string];
}@finally {
}
}
```
### 四、NSTimer
类型 | 原因
:---: | :---
NSTimer | 没有 invalidate,直接销毁
NSTimer | NStimer 与 target 强引用,内存泄漏
#### 解决方案:
- 交换`scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:`方法
- 定义一个抽象类`KJProxyProtector`,NSTimer实例强引用抽象类,而在抽象类中弱引用target,这样target和NSTimer之间的关系也就是弱引用,意味着target可以自由的释放,从而解决循环引用的问题
### 五、后台返回NSNull导致的崩溃
类型 | 原因
:---: | :---
NSNull | 后台返回NSNull导致的崩溃
#### 解决方案:
- 交换方法`methodSignatureForSelector:` 和 `forwardInvocation:`
### 六、UIKit Called on Non-Main Thread
#### 解决方案:
- 交换`setNeedsLayout`、`layoutIfNeeded`、`layoutSubviews`、`setNeedsUpdateConstraints`方法
- `[NSThread isMainThread]`判断当前线程是否为主线程,如果不是则在主线程执行
## 异常收集
### 一、防护类型
目前提供以下七种
```
typedef NS_OPTIONS(NSInteger, KJCrashProtectorType) {
KJCrashProtectorTypeContainer = 1 << 0,// 数组和字典
KJCrashProtectorTypeString = 1 << 1,// 字符串
KJCrashProtectorTypeUnrecognizedSelector = 1 << 2,// 没找到对应的函数
KJCrashProtectorTypeNSNull = 1 << 3,// 后台返回NSNull导致的崩溃
KJCrashProtectorTypeTimer = 1 << 4,// 计时器
KJCrashProtectorTypeKVO = 1 << 5,// kvo
KJCrashProtectorTypeUINonMain = 1 << 6,// 不在主线程中刷新UI
};
```
### 二、开启防护
采用多枚举方式,来快速设置需要开发的防护
没有合适的资源?快使用搜索试试~ 我知道了~
Foggy-master
共76个文件
m:18个
png:18个
h:16个
需积分: 9 0 下载量 156 浏览量
2022-07-29
13:53:41
上传
评论
收藏 653KB ZIP 举报
温馨提示
Foggy-master iOS app 奔溃治理
资源详情
资源评论
资源推荐
收起资源包目录
Foggy-master.zip (76个子文件)
Foggy-master
Foggy.xcodeproj
project.xcworkspace
contents.xcworkspacedata 135B
xcuserdata
dinghanlin.xcuserdatad
UserInterfaceState.xcuserstate 36KB
xcshareddata
IDEWorkspaceChecks.plist 238B
project.pbxproj 30KB
xcshareddata
xcschemes
Foggy.xcscheme 3KB
Images
AX.jpg 79KB
Foggy.podspec 1KB
LICENSE 1KB
.DS_Store 6KB
Foggy
AppDelegate.h 214B
AppDelegate.m 2KB
Resources
Assets.xcassets
AppIcon.appiconset
imagesiPhoneApp_60pt@2x.png 9KB
imagesiPhoneSpootlight7_40pt@3x.png 9KB
imagesiPhoneNotification_20pt@3x.png 4KB
Contents.json 3KB
imagesiPadSpootlight5_29pt.png 1KB
imagesiPadSpootlight7_40pt@2x.png 5KB
imagesiPhoneSpootlight7_40pt@2x.png 5KB
imagesiPadNotifications_20pt@2x.png 2KB
imagesiPhoneNotification_20pt@2x.png 2KB
imagesiPadApp_76pt@2x.png 13KB
imagesiPadProApp_83.5pt@2x.png 15KB
imagesiPhoneApp_60pt@3x.png 17KB
imagesiPadApp_76pt.png 5KB
imagesiPadNotifications_20pt.png 876B
imagesiPadSpootlight7_40pt.png 2KB
imagesstore_1024pt.png 267KB
imagesiPhoneSpootlight5_29pt@2x.png 4KB
imagesiPhoneSpootlight5_29pt@3x.png 6KB
imagesiPadSpootlight5_29pt@2x.png 4KB
Contents.json 63B
AX.imageset
AX.jpg 79KB
Contents.json 300B
main.m 431B
Foggy.podspec 405B
Base.lproj
LaunchScreen.storyboard 3KB
Main.storyboard 3KB
Info.plist 2KB
README.md 16KB
Sources
KJException.m 6KB
NSContainer
NSArray+KJException.h 439B
NSUserDefaults+KJException.h 298B
NSDictionary+KJException.h 454B
NSDictionary+KJException.m 6KB
NSUserDefaults+KJException.m 8KB
NSArray+KJException.m 7KB
KJCrashManager.h 971B
_Foggy.m 154B
NSTimer
NSTimer+KJException.h 329B
NSTimer+KJException.m 3KB
_Foggy.h 2KB
NSNull
NSNull+KJException.h 338B
NSNull+KJException.m 2KB
KJCrashManager.m 4KB
NSObject
NSObject+KJException.h 594B
NSObject+KJException.m 5KB
UINavigationController
UINavigationController+KJException.m 2KB
UINavigationController+KJException.h 447B
KJException.h 780B
NSString
NSString+KJException.h 491B
NSString+KJException.m 9KB
UIView
UIView+KJException.h 341B
UIView+KJException.m 2KB
Info.plist 1KB
Modules
OtherViewController.m 2KB
OtherViewController.h 214B
TestViewController.h 212B
ViewController.h 153B
TestViewController.m 1KB
ViewController.m 6KB
.gitignore 1KB
Document.md 16KB
FoggyTests
FoggyTests.m 791B
Info.plist 727B
README.md 7KB
.gitattributes 12B
共 76 条
- 1
weixin_38934440
- 粉丝: 18
- 资源: 10
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功
评论0