MonthNameFormatter数组越界崩溃问题排查求助
问题定位与修复:Swift数组越界崩溃
崩溃背景
长期无法解决的崩溃问题,Crashlytics与Xcode崩溃日志均指向自定义MonthNameFormatter类的stringForValue方法,触发Swift运行时数组越界错误,已有大量用户受影响。
Crashlytics崩溃日志
Crashed: com.apple.main-thread 0 FieldService 0x665cc specialized MonthNameFormater.stringForValue(_:axis:) + 4304774604 (GraphViewController.swift:4304774604) 1 FieldService 0x5fd74 @objc MonthNameFormater.stringForValue(_:axis:) + 4304747892 (:4304747892) 2 Charts 0xcc4c AxisBase.getFormattedLabel(_:) + 149 (AxisBase.swift:149) 3 Charts 0xcaa8 AxisBase.getLongestLabel() + 138 (AxisBase.swift:138) 4 Charts 0xb0c78 XAxisRenderer.computeSize() + 158 (XAxisRenderer.swift:158) 5 Charts 0xb0b3c XAxisRenderer.computeAxisValues(min:max:) + 154 (XAxisRenderer.swift:154) 6 Charts 0x1cb9c BarLineChartViewBase.notifyDataSetChanged() + 592 (:592) 7 Charts 0x57ee8 ChartViewBase.data.didset + 656 (:656) 8 Charts 0x54750 ChartViewBase.data.setter + 84 (:84) 9 FieldService 0x62368 GraphViewController.updateGraph() + 4304757608 (:4304757608) 10 FieldService 0x610e0 closure #1 in closure #1 in GraphViewController.viewDidLoad() + 4304752864 11 FieldService 0xb2000 thunk for @escaping @callee_guaranteed () -> () + 4305084416 (:4305084416) 12 libdispatch.dylib 0x1e6c _dispatch_call_block_and_release + 32 13 libdispatch.dylib 0x3a30 _dispatch_client_callout + 20 14 libdispatch.dylib 0x11f48 _dispatch_main_queue_drain + 928 15 libdispatch.dylib 0x11b98 _dispatch_main_queue_callback_4CF + 44 16 CoreFoundation 0x51800 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 16 17 CoreFoundation 0xb704 __CFRunLoopRun + 2532 18 CoreFoundation 0x1ebc8 CFRunLoopRunSpecific + 600 19 GraphicsServices 0x1374 GSEventRunModal + 164 20 UIKitCore 0x514b58 -[UIApplication _run] + 1100 21 UIKitCore 0x296098 UIApplicationMain + 364 22 FieldService 0x6b74 main + 20 (AppDelegate.swift:20) 23 ??? 0x1018a9da4 (Missing)
Xcode崩溃日志
Thread 0 name: Thread 0 Crashed: 0 FieldService 0x0000000104f665cc Swift runtime failure: Index out of range + 0 (:0) 1 FieldService 0x0000000104f665cc specialized _ArrayBuffer._checkInoutAndNativeTypeCheckedBounds(_:wasNativeTypeChecked:) + 0 (:0) 2 FieldService 0x0000000104f665cc specialized Array._checkSubscript(_:wasNativeTypeChecked:) + 0 (GraphViewController.swift:0) 3 FieldService 0x0000000104f665cc specialized Array.subscript.getter + 0 (GraphViewController.swift:17) 4 FieldService 0x0000000104f665cc specialized MonthNameFormater.stringForValue(_:axis:) + 744 5 FieldService 0x0000000104f66498 specialized MonthNameFormater.stringForValue(_:axis:) + 436 (:0) 6 FieldService 0x0000000104f5fd74 MonthNameFormater.stringForValue(_:axis:) + 4 (:0) 7 FieldService 0x0000000104f5fd74 @objc MonthNameFormater.stringForValue(_:axis:) + 16 8 Charts 0x0000000106b88c4c AxisBase.getFormattedLabel(_:) + 160 (AxisBase.swift:149) 9 Charts 0x0000000106b88aa8 implicit closure #2 in implicit closure #1 in AxisBase.getLongestLabel() + 8 (AxisBase.swift:138) 10 Charts 0x0000000106b88aa8 thunk for @escaping @callee_guaranteed (@unowned Int) -> (@owned String) + 8 (:0) 11 Charts 0x0000000106b88aa8 specialized Optional.map(_:) + 8 (:0) 12 Charts 0x0000000106b88aa8 specialized LazyMapSequence.Iterator.next() + 8 (:0) 13 Charts 0x0000000106b88aa8 specialized protocol witness for IteratorProtocol.next() in conformance LazyMapSequence.Iterator + 8 (:0)
相关代码
MonthNameFormatter类代码
import Charts import CoreData class MonthNameFormater: IndexAxisValueFormatter { override func stringForValue(_ value: Double, axis: AxisBase?) -> String { let symbols = Calendar.localizedCurrent.shortMonthSymbols let index = min(Options.shared.isTheocraticYearEnabled ? (Int(value) + 8) % 12 : Int(value), 11) guard symbols.count == 12 else { return "-" } return symbols[index] } }
使用代码
private let chartView: LineChartView = { let view = LineChartView() view.xAxis.labelPosition = .bottom view.rightAxis.enabled = false view.borderLineWidth = 0 view.xAxis.valueFormatter = MonthNameFormater() view.xAxis.granularity = 1 view.isUserInteractionEnabled = false return view }()
问题分析
崩溃的核心原因是计算出的index可能为负数,导致访问symbols数组时越界:
- 当
value是负数时,Int(value)会得到负数,即使加上8再取模12,在某些情况下仍可能得到负数(比如value = -9时,(-9 +8) %12 = -1%12 = -1)。 - 当前代码只限制了index的最大值为11,但没有处理负数情况,负数索引直接触发数组越界。
修复方案
修改index的计算逻辑,确保最终得到的index在0到11之间:
override func stringForValue(_ value: Double, axis: AxisBase?) -> String { let symbols = Calendar.localizedCurrent.shortMonthSymbols guard symbols.count == 12 else { return "-" } let rawIndex = Options.shared.isTheocraticYearEnabled ? (Int(value) + 8) : Int(value) // 处理负数,确保索引始终在0-11范围内 let index = (rawIndex % 12 + 12) % 12 return symbols[index] }
修复说明
(rawIndex %12 +12) %12可以确保无论rawIndex是正数还是负数,最终结果都是0到11之间的有效索引:- 正数情况:比如13 → 13%12=1 → 1+12=13 →13%12=1,结果正确。
- 负数情况:比如-1 → -1%12=-1 → -1+12=11 →11%12=11,结果有效;-13 →-13%12=-1 →-1+12=11 →11%12=11,同样有效。
- 同时将
symbols.count的校验提前,避免无效计算。
内容的提问来源于stack exchange,提问作者kunass2
相关产品推荐
相关产品推荐

