iOS 26 模拟器启动卡死:Method Swizzling 在系统回调时触发 nil 崩溃
一、现象
在 Xcode 26.4 + iOS 26.4 模拟器上运行项目,app 卡在 Launching 界面,始终无法进入主界面。控制台有大量 objc 类重复实现的警告(AuthKitUI / AuthKit 框架重复),但这些是系统 bug,与本次崩溃无关。
使用 LLDB 暂停进程,thread list 看到主线程异常:
thread #1: tid = 0xb124db, 0x0000000118fc9c10 CoreFoundation`-[__NSArrayM insertObject:atIndex:] + 251, queue = 'com.apple.main-thread'
二、定位过程
在 LLDB 中执行 thread select 1 + bt,得到完整调用栈:
frame #0: CoreFoundation`-[__NSArrayM insertObject:atIndex:] + 251
frame #1: FNCategory`-[NSMutableArray safe_insertObject:atIndex:] at NSMutableArray+FN.m:68
frame #2: FNCategory`-[NSMutableArray safe_addObject:] at NSMutableArray+FN.m:51
frame #3: CoreFoundation`-[NSEnumerator allObjects] + 189
frame #4: AXCoreUtilities`-[AXBinaryMonitor _frameworkNameForImage:]
frame #5: AXCoreUtilities`-[AXBinaryMonitor _handleLoadedImagePath:]
frame #6: AXCoreUtilities`___axmonitor_dyld_image_callback_block_invoke
关键结论:系统无障碍框架 AXCoreUtilities 在动态加载镜像(dyld image load)时,触发了一个回调,该回调内部调用了 NSEnumerator allObjects,而这个 allObjects 底层最终调用了 NSMutableArray addObject:。
由于项目通过 Method Swizzling 将系统的 addObject: 替换成了自定义的 safe_addObject:,这个系统内部调用被"劫持"进了我们的代码。
而 safe_addObject: 内部调用了 safe_insertObject:atIndex:,这里对 NSMutableArray 插入对象时发生了崩溃。
三、根本原因
这是一个经典的 Method Swizzling 副作用问题,iOS 26 改变了 AXCoreUtilities 的内部实现,触发了长期潜伏的 bug。
完整调用链如下:
-
AXCoreUtilities(系统无障碍框架)在 dyld 加载镜像时触发内部回调 - 回调内部操作了一个系统私有数组对象,调用了
insertObject:atIndex: - 由于 Method Swizzling,
insertObject:atIndex:已被替换成safe_insertObject:atIndex:,系统内部调用被"劫持"进了我们的代码 -
safe_insertObject:atIndex:内部再调用[self safe_insertObject:anObject atIndex:index](即原始方法),但此时self是系统内部的私有数组类型,不是普通的__NSArrayM,导致无限递归或调用到了错误的 IMP,最终崩溃
问题的本质是:Swizzling 作用在父类(NSMutableArray)上,但系统传入的是私有子类对象,Swizzling 后的方法实现与私有类的内存布局不兼容,在 iOS 26 收紧了 AXCoreUtilities 的调用时序之后,这个潜在冲突被激活。
正规的修复思路是在 SwizzlingMethod 里加类型保护,确保只 swizzle __NSArrayM 本身而不影响其私有子类。但由于 FNCategory 是 Pod,还有 AFNetworking、DoraemonKit 等我们无法直接修改源码的三方库存在同样问题,所以统一在 Podfile post_install 里做全局兼容处理。
四、踩过的坑
坑 1:以为是 objc 类重复警告导致的
启动时控制台打印了大量 Class AKAlertImageURLProvider is implemented in both AuthKitUI and AuthKit 的警告,误以为是这些重复类导致崩溃。实际上这是 iOS 26.4 模拟器运行时自身的打包问题,与启动卡死无关。
坑 2:只修复了 FNCategory,没有扩大范围
最初只在 FNCategory 的 NSMutableArray+FN.m 里加了 nil 保护,但 AFNetworking 和 DoraemonKit 也有同样模式的 Swizzling,同样存在风险。
五、修复方案
思路
不针对单个文件做字符串替换,而是在 Podfile 的 post_install 阶段,全局扫描所有 Pod 源文件,找到所有 method_exchangeImplementations( 调用,在其前面统一注入 nil 保护。
实现(Podfile post_install)
post_install do |installer|
# ... 其他 post_install 逻辑 ...
# 全局修复:为所有 Pod 的 method_exchangeImplementations 调用注入 nil 保护
# 防止 iOS 26 系统框架在 dyld 镜像加载回调中触发 Swizzled 方法时崩溃
fixed_count = 0
Dir.glob('Pods/**/*.{m,mm}').each do |file|
content = File.read(file)
next unless content.include?('method_exchangeImplementations(')
new_content = content.gsub(
/^(\s*)(method_exchangeImplementations\((\w+)\s*,\s*(\w+)\s*\)\s*;)/
) do
indent = $1
full_call = $2
arg1 = $3
arg2 = $4
"#{indent}if (#{arg1} && #{arg2}) #{full_call}"
end
if new_content != content
File.chmod(0644, file)
File.write(file, new_content)
puts "✅ 已修复 #{file} 的 method_exchangeImplementations nil 保护"
fixed_count += 1
end
end
puts "共修复 #{fixed_count} 处 method_exchangeImplementations nil 保护" if fixed_count > 0
end
修复效果
执行 pod install 后的输出:
✅ 已修复 Pods/AFNetworking/AFNetworking/AFNetworking/AFURLSessionManager.m 的 method_exchangeImplementations nil 保护
✅ 已修复 Pods/DoraemonKit/iOS/DoraemonKit/Src/Core/Category/NSObject+Doraemon.m 的 method_exchangeImplementations nil 保护
✅ 已修复 Pods/DoraemonKit/iOS/DoraemonKit/Src/Core/Plugin/Performance/StartTime/DoraemonStartTimeViewController.m 的 method_exchangeImplementations nil 保护
✅ 已修复 Pods/FNCategory/FNCategory/Classes/NSMutableArray+FN.m 的 method_exchangeImplementations nil 保护
✅ 已修复 Pods/FNCategory/FNCategory/Classes/NSObject+FNSwizzle.m 的 method_exchangeImplementations nil 保护
✅ 已修复 Pods/FNCategory/FNCategory/Classes/UIViewController+FNFullScreen.m 的 method_exchangeImplementations nil 保护
Integrating client project
Pod installation complete! There are 32 dependencies from the Podfile and 35 total pods installed.
共修复 6 处,涉及 AFNetworking、DoraemonKit、FNCategory 三个 Pod。
六、总结
| 项目 | 说明 |
|---|---|
| 问题类型 | Method Swizzling 缺少 nil 保护,被系统内部回调触发 |
| 触发条件 | iOS 26 改变了 dyld 镜像加载回调时序,在类注册完成前触发 Swizzle |
| 崩溃位置 |
NSMutableArray insertObject:atIndex: → safe_insertObject:atIndex:
|
| 修复方式 | Podfile post_install 全局注入 if (A && B) nil 保护 |
| 优点 | 一次修复,覆盖所有 Pod,无需逐个修改,pod update 后自动重新修复 |
| 注意 | 这是 Swizzling 的通用最佳实践,不局限于 iOS 26,建议所有项目都加上 |