本文是想通过一个例子来讲述,AI在修复Bug方面令人惊艳的能力。
一、传统方式下
先来看一个Crash日志的堆栈信息:
js
Termination Reason:<RBSTerminateContext| domain:10 code:0x8BADF00D
explanation:scene-create watchdog transgression: application<com.xxx.aaa>:
34689 exhausted real (wall clock) time allowance of 3.43 seconds
//
Thread 0 Crashed:
0 libsystem_pthread.dylib _pthread_mutex_lock$VARIANT$armv81 + 120
1 libc++.1.dylib std::__1::mutex::lock() + 12
2 libicucore.A.dylib icu::Locale::getDefault() + 32
3 libicucore.A.dylib icu::Locale::init(char const*, signed char) + 1400
4 libicucore.A.dylib _ures_getLocaleByType + 436
5 libicucore.A.dylib icu::DecimalFormatSymbols::initialize(icu::Locale const&, UErrorCode&, signed char, icu::NumberingSystem const*) + 256
6 libicucore.A.dylib icu::DecimalFormatSymbols::DecimalFormatSymbols(icu::Locale const&, icu::NumberingSystem const&, UErrorCode&) + 236
7 libicucore.A.dylib icu::number::LocalizedNumberFormatter::getDecimalFormatSymbols() const + 4608
8 libicucore.A.dylib icu::number::LocalizedNumberFormatter::getDecimalFormatSymbols() const + 1632
9 libicucore.A.dylib icu::number::LocalizedNumberFormatter::formatImpl(icu::number::impl::UFormattedNumberData*, UErrorCode&) const + 128
10 libicucore.A.dylib icu::SimpleDateFormat::zeroPaddingNumber(icu::NumberFormat const*, icu::UnicodeString&, int, int, int) const + 524
11 libicucore.A.dylib icu::SimpleDateFormat::subFormat(icu::UnicodeString&, char16_t, int, UDisplayContext, int, char16_t, icu::FieldPositionHandler&, icu::Calendar&, UErrorCode&) const + 904
12 libicucore.A.dylib icu::SimpleDateFormat::_format(icu::Calendar&, icu::UnicodeString&, icu::FieldPositionHandler&, UErrorCode&) const + 688
13 libicucore.A.dylib icu::SimpleDateFormat::format(icu::Calendar&, icu::UnicodeString&, icu::FieldPosition&) const + 80
14 libicucore.A.dylib icu::DateFormat::format(double, icu::UnicodeString&, icu::FieldPosition&) const + 124
15 libicucore.A.dylib _udat_format + 356
16 CoreFoundation ___cficu_udat_format + 64
17 CoreFoundation _CFDateFormatterCreateStringWithAbsoluteTime + 180
18 Foundation -[NSDateFormatter stringForObjectValue:] + 160
19 MyAPP -[HAMLaunchMonitor startUUID] + 136
... MYApp 其它调用函数
传统解决步骤:
- 查看HAMLaunchMonitor中startUUID方法里面内容,看代码上有没明显bug;
- 查看调用NSDateFormatter的string...方法的方式是否有问题;
- 后面是系统堆栈,所以得出结论是系统库发生了长时间等锁;
- 查看崩溃说明是
0x8BADF00D,确定是被系统WatchDog检测到杀死了进程导致的崩溃。 - 解决方式:少量偶现且逻辑不好修改,出临时性修复方案~
- 问题可能没有根本解决,只是概率降低了。
可以看到我们人工查看这个问题有几个瓶颈:
- 需要手动找到文件查看分析函数调用链,速度慢。
- 对于顶部的系统函数,我们一般是缺乏了解的,不知道里面发生了什么导致出了问题。
- 弄清楚了上面内容后,我们的解决方法也是基于自己认知的,存在偏差。
而这几个瓶颈,交给AI后那都不是事儿。
二、AI时代下Bug修复
复制堆栈信息交给AI工具(我目前用的是Qoder),AI直接给出了根本原因及有效的方案:
Crash 类型
这是一个 Watchdog 超时 Crash (
0x8BADF00D= "ate bad food"),发生在 scene-create(应用启动/场景创建) 阶段。系统允许的时间为 3.43 秒,但应用在该时间内未能完成启动。根因分析
从调用栈来看,崩溃发生在 [
-[HAMLaunchMonitor startUUID]] 方法中:
objc- (NSString *)startUUID { NSDateFormatter *formatter = [NSDateFormatter new]; formatter.dateFormat = @"yyMMddHHmmssSSS"; formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; NSString* time = [formatter stringFromDate:[NSDate date]]; // ... }主线程被阻塞在
pthread_mutex_lock上,具体路径是:
startUUID调用NSDateFormatter stringForObjectValue:进行日期格式化- 底层 ICU 库执行
SimpleDateFormat::format进行实际格式化- ICU 在格式化数字时需要获取
DecimalFormatSymbols- 这触发了
icu::Locale::getDefault()来获取系统默认 localegetDefault()内部需要获取一个 全局互斥锁(std::mutex)- 该锁被其他线程持有 ,导致主线程死等(
pthread_mutex_lock卡住)核心问题
锁竞争(Lock Contention) ------ ICU 库内部有一个全局的
std::mutex保护Locale::getDefault()。在应用启动阶段,如果有多个线程同时使用NSDateFormatter、NSNumberFormatter或其他依赖 ICU 的 API,就会争抢这把锁。主线程被阻塞等锁,累计时间超过 watchdog 限制,系统杀掉了应用。加剧因素
- 启动阶段调用
startUUID:该方法在 [commonParameters] 中被调用,而commonParameters在 [appDidFinishLaunch] 流程中执行------正处于 watchdog 监控最严格的时间窗口。- 每次调用都创建新的
NSDateFormatter:NSDateFormatter的初始化本身就是重量级操作,且会触发 ICU locale 的初始化。- ICU 内部的全局锁:这是系统库的已知问题,在 iOS 启动早期阶段锁竞争尤为严重,因为各种系统框架也在并发初始化。
建议修复方案
- 缓存
NSDateFormatter(最直接有效):将NSDateFormatter创建为静态变量或单例属性,避免每次调用startUUID都重新创建:
objc- (NSString *)startUUID { static NSDateFormatter *formatter = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ formatter = [NSDateFormatter new]; formatter.dateFormat = @"yyMMddHHmmssSSS"; formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; }); NSString *time = [formatter stringFromDate:[NSDate date]]; NSString *startUuid = [NSString stringWithFormat:@"%@2000000%@", time, [self getRandomNumber]]; return startUuid; }
- 延迟执行 :将
startUUID和上报逻辑延迟到首帧渲染之后,脱离 watchdog 监控的关键时间窗口。- 替代方案 :如果仅需要生成唯一标识,可以完全避免
NSDateFormatter,改用时间戳数字拼接: 这完全绕开了 ICU/NSDateFormatter,不存在锁竞争问题。
objc- (NSString *)startUUID { NSTimeInterval now = [[NSDate date] timeIntervalSince1970]; long long ms = (long long)(now * 1000); NSString *startUuid = [NSString stringWithFormat:@"%lld2000000%@", ms, [self getRandomNumber]]; return startUuid; }推荐方案 3,因为它从根本上消除了 ICU 锁竞争的风险,同时性能开销最小。
三、感想
AI时代,整体APP行业的性能将得到大幅提升
优秀大模型解决Bug的能力很强,且修复速度和质量都发生了颠覆性的变化,意味着如果有规划+APP开发人员有心,大部分问题都能解决;AI编程工具下,目前AI写复杂功能可能还有点问题,但如果是指定AI去写一个特定功能bug率可能会比资深工程师还要好;借助AI来深入了解底层知识也很方便,对于提升工程师认知也有帮助,进一步提升了性能。
AI时代,Bug的解决方式会发生变化
现在的热修复功能集成到APP后,往往需要编写修复后的脚本语言文件,下发到APP,APP动态运行时交换方法实现解决。
js
AI时代的方式可能是:
-》Crash发生后,自动分析原因,出解决方案,发出通知;
-》人工收到通知后,选择一个方案;
-》自动生成对应的脚本文件,自动下发到对应的APP版本。
-》APP再次打开时,Bug已经自动修复。