iOS Crash 本质与捕获修复方案

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                                              │
│     ├── 符号化堆栈                                                    │
│     ├── 补充设备/用户上下文                                           │
│     └── 建立崩溃率监控告警                                            │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘
相关推荐
喵霓1 小时前
mac—android-platform-tools介绍
android·macos
malajisi011 小时前
鸿蒙PC开发笔记一:HarmonyOS PC 命令行适配指南(Mac 版)
笔记·macos·harmonyos·harmony·鸿蒙pc·harmony pc
喵霓1 小时前
mac——安装wget
macos
bioinforiver1 小时前
蛋白质设计(六)— — 三剑合璧,mac如何安装ProteinMPNN、Alphafold2
macos
Charles Shan1 小时前
Mac上的linux虚拟机踩坑日记
linux·macos
2501_915106321 小时前
iPhone 耗电异常全面诊断指南,构建多工具协同的电量分析与优化体系
android·ios·小程序·https·uni-app·iphone·webview
老菜鸟YDZ1 小时前
二十多年前的苹果电脑Power Mac G4 MDD音箱修复
macos·苹果电脑·power mac g4·音箱修复
Sheffi662 小时前
iOS 卡顿监控实现:RunLoop + 堆栈采样
ios
2301_766536052 小时前
MacOS操作杂谈
macos