You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 03:50:20