Crash 的本质
复制代码
┌─────────────────────────────────────────────────────────────────────────┐
│ iOS Crash 本质 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ App 运行中发生错误 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 两种底层机制 │ │
│ ├──────────────────────────┬──────────────────────────────────────┤ │
│ │ │ │ │
│ │ Mach 异常 │ Unix 信号 (Signal) │ │
│ │ (内核层) │ (BSD 层) │ │
│ │ │ │ │
│ │ • EXC_BAD_ACCESS │ • SIGSEGV (段错误) │ │
│ │ • EXC_BAD_INSTRUCTION │ • SIGBUS (总线错误) │ │
│ │ • EXC_ARITHMETIC │ • SIGABRT (主动终止) │ │
│ │ • EXC_CRASH │ • SIGFPE (算术异常) │ │
│ │ │ • SIGILL (非法指令) │ │
│ │ │ • SIGTRAP (断点) │ │
│ └──────────────────────────┴──────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ OC 层异常 (NSException) │ │
│ │ │ │
│ │ • NSInvalidArgumentException (参数无效) │ │
│ │ • NSRangeException (数组越界) │ │
│ │ • NSGenericException (通用异常) │ │
│ │ • NSInternalInconsistencyException (内部不一致) │ │
│ │ │ │
│ │ ↓ 未捕获时转换为 │ │
│ │ SIGABRT 信号 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
异常传递链
复制代码
┌────────────────────────────────────────────────────────────────────────┐
│ 异常传递流程 │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ 硬件/内核错误 │ 例: 访问无效内存地址 │
│ └──────┬──────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Mach 异常 │ │
│ │ 内核检测到异常,发送 Mach Exception Message │ │
│ │ 可通过 mach_port 捕获 (优先级最高) │ │
│ └──────────────────────────┬──────────────────────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ BSD Signal │ │
│ │ Mach 异常被 ux_exception() 转换为 Unix Signal │ │
│ │ 可通过 signal() / sigaction() 捕获 │ │
│ └──────────────────────────┬──────────────────────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 进程终止 │ │
│ │ 如果信号未处理,进程被 kill │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────┘
常见崩溃类型详解
1. 崩溃类型分类表
| 类型 |
信号 |
常见原因 |
示例 |
| 内存访问错误 |
SIGSEGV/SIGBUS |
野指针、越界访问 |
EXC_BAD_ACCESS |
| OC 异常 |
SIGABRT |
未捕获的 NSException |
数组越界、方法找不到 |
| 主动终止 |
SIGABRT |
assert 失败、abort() |
NSAssert 触发 |
| 非法指令 |
SIGILL |
执行无效机器码 |
代码段损坏 |
| 算术异常 |
SIGFPE |
除零等 |
整数除以 0 |
| 资源耗尽 |
SIGKILL |
内存溢出、CPU 超时 |
OOM、Watchdog |
2. 详细崩溃场景
objc
复制代码
// ==================== 1. 野指针 (EXC_BAD_ACCESS) ====================
/*
本质: 访问已释放/未分配的内存地址
信号: SIGSEGV (Segmentation Fault)
*/
// 场景1: 对象提前释放
__unsafe_unretained id unsafeObj;
@autoreleasepool {
unsafeObj = [[NSObject alloc] init];
}
[unsafeObj description]; // 💥 Crash: 对象已释放
// 场景2: 多线程同时操作
dispatch_async(queue1, ^{
[self.mutableArray addObject:@"A"];
});
dispatch_async(queue2, ^{
[self.mutableArray removeAllObjects]; // 💥 可能 Crash
});
// 场景3: Block 内访问已释放对象
__block __unsafe_unretained ViewController *weakSelf = self;
dispatch_after(delay, queue, ^{
[weakSelf doSomething]; // 💥 self 可能已释放
});
// ==================== 2. 数组越界 (NSRangeException) ====================
/*
本质: 访问超出集合边界的索引
信号: SIGABRT (通过 NSException -> abort)
*/
NSArray *array = @[@1, @2, @3];
id obj = array[5]; // 💥 Crash: index 5 beyond bounds [0..2]
NSMutableArray *mArray = [NSMutableArray array];
[mArray objectAtIndex:0]; // 💥 Crash: 空数组
// ==================== 3. 方法找不到 (Unrecognized Selector) ====================
/*
本质: 向对象发送未实现的消息
信号: SIGABRT
*/
NSString *str = @"hello";
[(id)str addObject:@"x"]; // 💥 Crash: unrecognized selector
// 类型混淆
id data = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil];
// data 可能是 NSDictionary 或 NSArray
NSString *value = [data objectForKey:@"key"]; // 💥 如果是 NSArray 就崩溃
// ==================== 4. KVO 崩溃 ====================
/*
本质: KVO 添加/移除不匹配,或被观察对象提前释放
*/
// 场景1: 重复移除
[obj removeObserver:self forKeyPath:@"name"];
[obj removeObserver:self forKeyPath:@"name"]; // 💥 Crash
// 场景2: 未移除就释放
- (void)dealloc {
// 忘记 removeObserver
// 💥 被观察对象发送通知时 Crash
}
// 场景3: 观察者释放但未移除
// obj 还持有对已释放 observer 的引用
// ==================== 5. 多线程崩溃 ====================
/*
本质: 非线程安全的操作在多线程环境执行
*/
// 场景1: 非原子性操作
@property (nonatomic, strong) NSMutableDictionary *cache;
// Thread A
[self.cache setObject:value forKey:key];
// Thread B (同时)
[self.cache removeObjectForKey:key]; // 💥 内部结构损坏
// 场景2: 边遍历边修改
for (id obj in self.mutableArray) {
if ([self shouldRemove:obj]) {
[self.mutableArray removeObject:obj]; // 💥 Crash
}
}
// ==================== 6. 主线程 UI 崩溃 ====================
/*
本质: 在非主线程操作 UI
*/
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.label.text = @"Hello"; // ⚠️ 可能崩溃或 UI 异常
[self.tableView reloadData]; // 💥 高概率崩溃
});
// ==================== 7. 内存溢出 (OOM) ====================
/*
本质: 内存使用超过系统限制
信号: SIGKILL (无法捕获)
*/
// 场景1: 大图片未压缩
UIImage *hugeImage = [UIImage imageWithContentsOfFile:path]; // 10000x10000
imageView.image = hugeImage; // 💥 OOM
// 场景2: 循环引用导致内存泄漏累积
self.block = ^{
[self doSomething]; // 循环引用,内存不释放
};
// ==================== 8. 死锁 (Watchdog) ====================
/*
本质: 主线程阻塞超过系统允许时间(约 10-20 秒)
信号: SIGKILL (无法捕获)
*/
// 场景1: 主线程同步等待
dispatch_sync(dispatch_get_main_queue(), ^{
// 💥 死锁: 主线程等待自己
});
// 场景2: 锁嵌套
pthread_mutex_lock(&mutexA);
pthread_mutex_lock(&mutexB);
// 另一个线程相反顺序加锁 → 死锁
完整崩溃捕获实现
1. 崩溃捕获器
objc
复制代码
// CrashCatcher.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// 崩溃类型
typedef NS_ENUM(NSUInteger, CrashType) {
CrashTypeSignal, // 信号崩溃
CrashTypeException, // OC 异常
CrashTypeCPP // C++ 异常
};
/// 崩溃信息
@interface CrashInfo : NSObject
@property (nonatomic, assign) CrashType type;
@property (nonatomic, copy) NSString *name; // 异常名/信号名
@property (nonatomic, copy) NSString *reason; // 原因
@property (nonatomic, copy) NSArray<NSString *> *callStack;
@property (nonatomic, strong) NSDate *timestamp;
@property (nonatomic, copy) NSDictionary *context;
@end
/// 崩溃捕获器
@interface CrashCatcher : NSObject
+ (void)startWithCallback:(void(^)(CrashInfo *crash))callback;
+ (void)stop;
/// 获取上次崩溃信息(启动时调用)
+ (nullable CrashInfo *)lastCrashInfo;
@end
NS_ASSUME_NONNULL_END
objc
复制代码
// CrashCatcher.m
#import "CrashCatcher.h"
#import <execinfo.h>
#import <sys/signal.h>
#import <mach/mach.h>
// 需要捕获的信号
static const int kFatalSignals[] = {
SIGABRT, // 调用 abort()
SIGBUS, // 总线错误
SIGFPE, // 浮点异常
SIGILL, // 非法指令
SIGSEGV, // 段错误
SIGTRAP, // 断点
SIGSYS // 非法系统调用
};
static const int kFatalSignalCount = sizeof(kFatalSignals) / sizeof(kFatalSignals[0]);
// 保存原有的信号处理器
static struct sigaction *g_previousSignalHandlers = NULL;
// 保存原有的异常处理器
static NSUncaughtExceptionHandler *g_previousExceptionHandler = NULL;
// 回调
static void(^g_crashCallback)(CrashInfo *crash) = nil;
// 崩溃信息保存路径
static NSString *g_crashInfoPath = nil;
@implementation CrashInfo
@end
#pragma mark - Signal Handler
static void SignalHandler(int signal, siginfo_t *info, void *context) {
// 在信号处理器中,只能调用 async-signal-safe 的函数
// 但为了获取更多信息,我们这里做一些必要操作
// 1. 获取信号名
NSString *signalName = [CrashCatcher signalName:signal];
// 2. 获取堆栈
void *callstack[128];
int frames = backtrace(callstack, 128);
char **symbols = backtrace_symbols(callstack, frames);
NSMutableArray *stack = [NSMutableArray array];
for (int i = 0; i < frames; i++) {
[stack addObject:[NSString stringWithUTF8String:symbols[i]]];
}
free(symbols);
// 3. 构建崩溃信息
CrashInfo *crash = [[CrashInfo alloc] init];
crash.type = CrashTypeSignal;
crash.name = signalName;
crash.reason = [NSString stringWithFormat:@"Signal %d was raised", signal];
crash.callStack = stack;
crash.timestamp = [NSDate date];
crash.context = @{
@"signal": @(signal),
@"faultAddress": @((uintptr_t)info->si_addr)
};
// 4. 保存到本地(因为应用即将终止)
[CrashCatcher saveCrashInfo:crash];
// 5. 回调
if (g_crashCallback) {
g_crashCallback(crash);
}
// 6. 调用之前的处理器
struct sigaction *previous = &g_previousSignalHandlers[signal];
if (previous->sa_flags & SA_SIGINFO) {
if (previous->sa_sigaction) {
previous->sa_sigaction(signal, info, context);
}
} else if (previous->sa_handler != SIG_DFL && previous->sa_handler != SIG_IGN) {
previous->sa_handler(signal);
}
// 7. 重新抛出信号(让应用正常终止)
signal(signal, SIG_DFL);
raise(signal);
}
#pragma mark - Exception Handler
static void ExceptionHandler(NSException *exception) {
// 1. 获取堆栈
NSArray *stack = [exception callStackSymbols];
if (!stack || stack.count == 0) {
stack = [NSThread callStackSymbols];
}
// 2. 构建崩溃信息
CrashInfo *crash = [[CrashInfo alloc] init];
crash.type = CrashTypeException;
crash.name = exception.name;
crash.reason = exception.reason;
crash.callStack = stack;
crash.timestamp = [NSDate date];
crash.context = @{
@"userInfo": exception.userInfo ?: @{}
};
// 3. 保存
[CrashCatcher saveCrashInfo:crash];
// 4. 回调
if (g_crashCallback) {
g_crashCallback(crash);
}
// 5. 调用之前的处理器
if (g_previousExceptionHandler) {
g_previousExceptionHandler(exception);
}
}
@implementation CrashCatcher
#pragma mark - Public
+ (void)startWithCallback:(void (^)(CrashInfo *))callback {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
g_crashCallback = callback;
g_crashInfoPath = [self crashInfoPath];
[self installSignalHandlers];
[self installExceptionHandler];
});
}
+ (void)stop {
[self uninstallSignalHandlers];
[self uninstallExceptionHandler];
}
+ (CrashInfo *)lastCrashInfo {
NSData *data = [NSData dataWithContentsOfFile:g_crashInfoPath];
if (!data) return nil;
@try {
CrashInfo *crash = [NSKeyedUnarchiver unarchivedObjectOfClass:[CrashInfo class]
fromData:data
error:nil];
// 读取后删除
[[NSFileManager defaultManager] removeItemAtPath:g_crashInfoPath error:nil];
return crash;
} @catch (NSException *exception) {
return nil;
}
}
#pragma mark - Signal Handlers
+ (void)installSignalHandlers {
// 分配保存旧处理器的空间
g_previousSignalHandlers = calloc(32, sizeof(struct sigaction));
struct sigaction action;
memset(&action, 0, sizeof(action));
sigemptyset(&action.sa_mask);
action.sa_flags = SA_SIGINFO | SA_ONSTACK;
action.sa_sigaction = SignalHandler;
for (int i = 0; i < kFatalSignalCount; i++) {
int signal = kFatalSignals[i];
// 保存原有处理器
sigaction(signal, NULL, &g_previousSignalHandlers[signal]);
// 安装新处理器
sigaction(signal, &action, NULL);
}
}
+ (void)uninstallSignalHandlers {
for (int i = 0; i < kFatalSignalCount; i++) {
int signal = kFatalSignals[i];
sigaction(signal, &g_previousSignalHandlers[signal], NULL);
}
free(g_previousSignalHandlers);
g_previousSignalHandlers = NULL;
}
#pragma mark - Exception Handler
+ (void)installExceptionHandler {
g_previousExceptionHandler = NSGetUncaughtExceptionHandler();
NSSetUncaughtExceptionHandler(ExceptionHandler);
}
+ (void)uninstallExceptionHandler {
NSSetUncaughtExceptionHandler(g_previousExceptionHandler);
g_previousExceptionHandler = nil;
}
#pragma mark - Helpers
+ (NSString *)signalName:(int)signal {
switch (signal) {
case SIGABRT: return @"SIGABRT";
case SIGBUS: return @"SIGBUS";
case SIGFPE: return @"SIGFPE";
case SIGILL: return @"SIGILL";
case SIGSEGV: return @"SIGSEGV";
case SIGTRAP: return @"SIGTRAP";
case SIGSYS: return @"SIGSYS";
default: return [NSString stringWithFormat:@"Signal(%d)", signal];
}
}
+ (NSString *)crashInfoPath {
NSString *doc = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES).firstObject;
return [doc stringByAppendingPathComponent:@"last_crash.dat"];
}
+ (void)saveCrashInfo:(CrashInfo *)crash {
@try {
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:crash
requiringSecureCoding:NO
error:nil];
[data writeToFile:g_crashInfoPath atomically:YES];
} @catch (NSException *exception) {
// 忽略保存错误
}
}
@end
2. Mach 异常捕获(更底层)
objc
复制代码
// MachExceptionHandler.m
#import <mach/mach.h>
#import <pthread.h>
static mach_port_t g_exceptionPort = MACH_PORT_NULL;
static pthread_t g_exceptionThread;
static BOOL g_isMonitoring = NO;
// 异常处理线程
static void *ExceptionHandlerThread(void *arg) {
while (g_isMonitoring) {
// 接收异常消息
struct {
mach_msg_header_t header;
mach_msg_body_t body;
mach_msg_port_descriptor_t thread;
mach_msg_port_descriptor_t task;
NDR_record_t ndr;
exception_type_t exception;
mach_msg_type_number_t codeCount;
int64_t code[2];
} message;
mach_msg_return_t ret = mach_msg(
&message.header,
MACH_RCV_MSG | MACH_RCV_LARGE,
0,
sizeof(message),
g_exceptionPort,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL
);
if (ret != MACH_MSG_SUCCESS) {
continue;
}
// 获取异常信息
exception_type_t exceptionType = message.exception;
thread_t thread = message.thread.name;
NSLog(@"[Mach] Exception: %d on thread: %d", exceptionType, thread);
// 获取线程堆栈
// ... (与之前的堆栈采样类似)
// 注意: 这里应该将异常转发给原有的处理器
// 否则应用不会正常崩溃,可能导致僵尸状态
}
return NULL;
}
void InstallMachExceptionHandler(void) {
// 创建异常端口
mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &g_exceptionPort);
mach_port_insert_right(mach_task_self(), g_exceptionPort, g_exceptionPort,
MACH_MSG_TYPE_MAKE_SEND);
// 设置异常端口
task_set_exception_ports(
mach_task_self(),
EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION |
EXC_MASK_ARITHMETIC | EXC_MASK_CRASH,
g_exceptionPort,
EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES,
THREAD_STATE_NONE
);
// 启动异常处理线程
g_isMonitoring = YES;
pthread_create(&g_exceptionThread, NULL, ExceptionHandlerThread, NULL);
}
防崩溃方案实现
1. 安全容器
objc
复制代码
// SafeArray.h - 线程安全 + 越界保护
@interface SafeArray<ObjectType> : NSObject
- (void)addObject:(ObjectType)object;
- (void)removeObject:(ObjectType)object;
- (void)removeObjectAtIndex:(NSUInteger)index;
- (nullable ObjectType)objectAtIndex:(NSUInteger)index;
- (NSUInteger)count;
- (void)enumerateObjectsUsingBlock:(void(^)(ObjectType obj, NSUInteger idx, BOOL *stop))block;
@end
// SafeArray.m
@implementation SafeArray {
NSMutableArray *_array;
dispatch_queue_t _queue;
}
- (instancetype)init {
if (self = [super init]) {
_array = [NSMutableArray array];
_queue = dispatch_queue_create("com.safearray.queue",
DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
- (void)addObject:(id)object {
if (!object) return;
dispatch_barrier_async(_queue, ^{
[self->_array addObject:object];
});
}
- (void)removeObject:(id)object {
if (!object) return;
dispatch_barrier_async(_queue, ^{
[self->_array removeObject:object];
});
}
- (void)removeObjectAtIndex:(NSUInteger)index {
dispatch_barrier_async(_queue, ^{
if (index < self->_array.count) {
[self->_array removeObjectAtIndex:index];
}
});
}
- (id)objectAtIndex:(NSUInteger)index {
__block id result = nil;
dispatch_sync(_queue, ^{
if (index < self->_array.count) {
result = self->_array[index];
}
});
return result;
}
- (NSUInteger)count {
__block NSUInteger result = 0;
dispatch_sync(_queue, ^{
result = self->_array.count;
});
return result;
}
- (void)enumerateObjectsUsingBlock:(void (^)(id, NSUInteger, BOOL *))block {
dispatch_sync(_queue, ^{
// 复制一份遍历,避免遍历过程中被修改
[[self->_array copy] enumerateObjectsUsingBlock:block];
});
}
@end
2. Unrecognized Selector 防护
objc
复制代码
// NSObject+SelectorProtection.m
#import <objc/runtime.h>
@implementation NSObject (SelectorProtection)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Hook forwardingTargetForSelector:
Method original = class_getInstanceMethod(self, @selector(forwardingTargetForSelector:));
Method swizzled = class_getInstanceMethod(self, @selector(safe_forwardingTargetForSelector:));
method_exchangeImplementations(original, swizzled);
});
}
- (id)safe_forwardingTargetForSelector:(SEL)aSelector {
// 先调用原始实现
id target = [self safe_forwardingTargetForSelector:aSelector];
if (target) return target;
// 如果没有转发目标,检查是否需要保护
// 排除系统类和特殊类
NSString *className = NSStringFromClass([self class]);
if ([className hasPrefix:@"NS"] ||
[className hasPrefix:@"UI"] ||
[className hasPrefix:@"_"]) {
return nil;
}
// 返回保护对象,吞掉这个调用
NSLog(@"⚠️ [SelectorProtection] %@ does not respond to %@",
className, NSStringFromSelector(aSelector));
// 上报
[self reportUnrecognizedSelector:aSelector];
// 返回一个能响应任意方法的对象
return [UnrecognizedSelectorProtector shared];
}
- (void)reportUnrecognizedSelector:(SEL)selector {
// 上报到监控后台
NSDictionary *info = @{
@"class": NSStringFromClass([self class]),
@"selector": NSStringFromSelector(selector),
@"stack": [NSThread callStackSymbols]
};
// 发送...
}
@end
// 保护对象 - 能响应任意方法
@interface UnrecognizedSelectorProtector : NSObject
+ (instancetype)shared;
@end
@implementation UnrecognizedSelectorProtector
+ (instancetype)shared {
static UnrecognizedSelectorProtector *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
// 动态添加方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
class_addMethod(self, sel, (IMP)emptyMethod, "v@:");
return YES;
}
static void emptyMethod(id self, SEL _cmd) {
// 空实现,什么都不做
}
// 备选:返回方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
// 什么都不做
}
@end
3. KVO 防护
objc
复制代码
// NSObject+SafeKVO.m
#import <objc/runtime.h>
// KVO 信息存储
@interface KVOInfo : NSObject
@property (nonatomic, weak) id observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, assign) void *context;
@end
@implementation KVOInfo
@end
@implementation NSObject (SafeKVO)
- (NSMutableDictionary<NSString *, NSMutableArray<KVOInfo *> *> *)kvoInfoMap {
NSMutableDictionary *map = objc_getAssociatedObject(self, _cmd);
if (!map) {
map = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, _cmd, map, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return map;
}
- (void)safe_addObserver:(id)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(void *)context {
if (!observer || !keyPath) return;
@synchronized (self) {
NSMutableDictionary *map = [self kvoInfoMap];
NSMutableArray *infos = map[keyPath];
if (!infos) {
infos = [NSMutableArray array];
map[keyPath] = infos;
}
// 检查是否重复添加
for (KVOInfo *info in infos) {
if (info.observer == observer) {
NSLog(@"⚠️ [SafeKVO] 重复添加 observer:%@ keyPath:%@", observer, keyPath);
return;
}
}
// 记录
KVOInfo *info = [[KVOInfo alloc] init];
info.observer = observer;
info.keyPath = keyPath;
info.context = context;
[infos addObject:info];
// 调用原始方法
[self addObserver:observer forKeyPath:keyPath options:options context:context];
}
}
- (void)safe_removeObserver:(id)observer forKeyPath:(NSString *)keyPath {
if (!observer || !keyPath) return;
@synchronized (self) {
NSMutableDictionary *map = [self kvoInfoMap];
NSMutableArray *infos = map[keyPath];
KVOInfo *toRemove = nil;
for (KVOInfo *info in infos) {
if (info.observer == observer) {
toRemove = info;
break;
}
}
if (!toRemove) {
NSLog(@"⚠️ [SafeKVO] 尝试移除未添加的 observer:%@ keyPath:%@", observer, keyPath);
return;
}
[infos removeObject:toRemove];
[self removeObserver:observer forKeyPath:keyPath];
}
}
// 在 dealloc 时自动移除所有 observer
- (void)safe_dealloc {
@synchronized (self) {
NSMutableDictionary *map = [self kvoInfoMap];
for (NSString *keyPath in map) {
NSMutableArray *infos = map[keyPath];
for (KVOInfo *info in infos) {
if (info.observer) {
@try {
[self removeObserver:info.observer forKeyPath:keyPath];
} @catch (NSException *exception) {
// 忽略
}
}
}
}
[map removeAllObjects];
}
[self safe_dealloc]; // 调用原始 dealloc
}
@end
4. 容器类保护(Category)
objc
复制代码
// NSArray+Safe.m
#import <objc/runtime.h>
@implementation NSArray (Safe)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// __NSArrayI 是不可变数组的真实类
Class cls = NSClassFromString(@"__NSArrayI");
[self swizzleClass:cls original:@selector(objectAtIndex:)
swizzled:@selector(safe_objectAtIndex:)];
[self swizzleClass:cls original:@selector(objectAtIndexedSubscript:)
swizzled:@selector(safe_objectAtIndexedSubscript:)];
});
}
+ (void)swizzleClass:(Class)cls original:(SEL)original swizzled:(SEL)swizzled {
Method originalMethod = class_getInstanceMethod(cls, original);
Method swizzledMethod = class_getInstanceMethod(self, swizzled);
if (originalMethod && swizzledMethod) {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
- (id)safe_objectAtIndex:(NSUInteger)index {
if (index >= self.count) {
NSLog(@"⚠️ [SafeArray] 越界访问 index:%lu count:%lu",
(unsigned long)index, (unsigned long)self.count);
[self reportArrayOutOfBounds:index];
return nil;
}
return [self safe_objectAtIndex:index];
}
- (id)safe_objectAtIndexedSubscript:(NSUInteger)index {
if (index >= self.count) {
NSLog(@"⚠️ [SafeArray] 越界访问 index:%lu count:%lu",
(unsigned long)index, (unsigned long)self.count);
[self reportArrayOutOfBounds:index];
return nil;
}
return [self safe_objectAtIndexedSubscript:index];
}
- (void)reportArrayOutOfBounds:(NSUInteger)index {
// 上报
}
@end
// NSMutableArray+Safe.m
@implementation NSMutableArray (Safe)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = NSClassFromString(@"__NSArrayM");
[self swizzle:cls sel:@selector(addObject:) with:@selector(safe_addObject:)];
[self swizzle:cls sel:@selector(insertObject:atIndex:) with:@selector(safe_insertObject:atIndex:)];
[self swizzle:cls sel:@selector(removeObjectAtIndex:) with:@selector(safe_removeObjectAtIndex:)];
[self swizzle:cls sel:@selector(replaceObjectAtIndex:withObject:)
with:@selector(safe_replaceObjectAtIndex:withObject:)];
});
}
- (void)safe_addObject:(id)anObject {
if (!anObject) {
NSLog(@"⚠️ [SafeMutableArray] 尝试添加 nil");
return;
}
[self safe_addObject:anObject];
}
- (void)safe_insertObject:(id)anObject atIndex:(NSUInteger)index {
if (!anObject) {
NSLog(@"⚠️ [SafeMutableArray] 尝试插入 nil");
return;
}
if (index > self.count) {
NSLog(@"⚠️ [SafeMutableArray] 插入越界 index:%lu count:%lu",
(unsigned long)index, (unsigned long)self.count);
return;
}
[self safe_insertObject:anObject atIndex:index];
}
- (void)safe_removeObjectAtIndex:(NSUInteger)index {
if (index >= self.count) {
NSLog(@"⚠️ [SafeMutableArray] 删除越界 index:%lu count:%lu",
(unsigned long)index, (unsigned long)self.count);
return;
}
[self safe_removeObjectAtIndex:index];
}
- (void)safe_replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject {
if (!anObject) {
NSLog(@"⚠️ [SafeMutableArray] 尝试替换为 nil");
return;
}
if (index >= self.count) {
NSLog(@"⚠️ [SafeMutableArray] 替换越界");
return;
}
[self safe_replaceObjectAtIndex:index withObject:anObject];
}
@end
// NSDictionary+Safe.m
@implementation NSDictionary (Safe)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Hook dictionaryWithObjects:forKeys:count: 防止 nil key/value
Method original = class_getClassMethod(self, @selector(dictionaryWithObjects:forKeys:count:));
Method swizzled = class_getClassMethod(self, @selector(safe_dictionaryWithObjects:forKeys:count:));
method_exchangeImplementations(original, swizzled);
});
}
+ (instancetype)safe_dictionaryWithObjects:(const id [])objects
forKeys:(const id<NSCopying> [])keys
count:(NSUInteger)cnt {
NSMutableArray *validObjects = [NSMutableArray arrayWithCapacity:cnt];
NSMutableArray *validKeys = [NSMutableArray arrayWithCapacity:cnt];
for (NSUInteger i = 0; i < cnt; i++) {
if (objects[i] && keys[i]) {
[validObjects addObject:objects[i]];
[validKeys addObject:keys[i]];
} else {
NSLog(@"⚠️ [SafeDictionary] 过滤 nil key/value at index %lu", (unsigned long)i);
}
}
return [self safe_dictionaryWithObjects:validObjects.copy
forKeys:validKeys.copy
count:validObjects.count];
}
@end
最佳实践总结
复制代码
┌─────────────────────────────────────────────────────────────────────┐
│ 防崩溃最佳实践 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 编码规范 │
│ ├── 使用 weak 而非 unsafe_unretained │
│ ├── 集合操作前检查边界 │
│ ├── 对外部数据做类型校验 │
│ └── 多线程操作使用锁或串行队列 │
│ │
│ 2. 工具辅助 │
│ ├── 开启 Address Sanitizer (开发期) │
│ ├── 开启 Thread Sanitizer (开发期) │
│ ├── 使用 Zombie Objects 检测野指针 │
│ └── 静态分析 (Analyze) │
│ │
│ 3. 运行时防护 │
│ ├── 容器类安全封装 │
│ ├── Unrecognized Selector 防护 │
│ ├── KVO 安全管理 │
│ └── 主线程 UI 检查 │
│ │
│ 4. 监控上报 │
│ ├── 集成崩溃收集 SDK │
│ ├── 符号化堆栈 │
│ ├── 补充设备/用户上下文 │
│ └── 建立崩溃率监控告警 │
│ │
└─────────────────────────────────────────────────────────────────────┘