UICalendarView装饰缓存触发NSInvalidArgumentException错误排查
UICalendarView缓存日期装饰时触发NSInvalidArgumentException错误
问题复现
代码示例
class Coordinator: NSObject, UICalendarViewDelegate, UICalendarSelectionSingleDateDelegate { // ... private var decorationCache: [DateComponents: UICalendarView.Decoration?] = [:] func calendarView(_ calendarView: UICalendarView, decorationFor dateComponents: DateComponents) -> UICalendarView.Decoration? { if let decoration = decorationCache[dateComponents] { return decoration } let decoration = calculateDecoration(dateComponents: dateComponents) decorationCache[dateComponents] = decoration return decoration } // ... }
错误信息
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
即使将calculateDecoration(dateComponents:)替换为直接返回UICalendarView.Decoration.default(),崩溃依然会触发。
调用栈关键片段
* thread #1, queue = 'com.apple.uikit.datasource.diffing', stop reason = breakpoint 1.1 * frame #0: 0x0000000100abf6e4 JobShift`CalendarView.Coordinator.calendarView(calendarView=0x00000001212052d0, dateComponents=Foundation.DateComponents @ 0x000000016f3d7850, self=0x0000000121204fe0) at CalendarView.swift:47:24 frame #1: 0x0000000100abf880 JobShift`@objc CalendarView.Coordinator.calendarView(_:decorationFor:) at <compiler-generated>:0 frame #2: 0x00000001aa753d60 UIKitCore`-[UICalendarView _delegateDecorationForDay:] + 108 frame #3: 0x00000001aa753bdc UIKitCore`-[UICalendarView _configuedCellForCollectionView:indexPath:day:] + 172
错误原因
问题出在缓存字典的类型定义上:
private var decorationCache: [DateComponents: UICalendarView.Decoration?] = [:]
这里字典的值类型是双重可选类型(UICalendarView.Decoration??)。当首次查询某个DateComponents时,字典中没有对应键,返回的nil会被自动包装成Optional(nil)(即UICalendarView.Decoration??.some(nil))。
当这个值被返回给calendarView(_:decorationFor:)方法后,UIKit内部尝试将该值插入到NSMutableArray中,但Foundation的数组不允许存储nil对象,因此触发了-[__NSArrayM insertObject:atIndex:]: object cannot be nil的崩溃。
修复方案
方案1:保留nil缓存,调整查询逻辑
通过检查字典键是否存在,区分“未计算”和“已计算(含nil)”的状态:
class Coordinator: NSObject, UICalendarViewDelegate, UICalendarSelectionSingleDateDelegate { // ... private var decorationCache: [DateComponents: UICalendarView.Decoration?] = [:] func calendarView(_ calendarView: UICalendarView, decorationFor dateComponents: DateComponents) -> UICalendarView.Decoration? { // 先判断键是否存在,避免将"未计算"的nil包装成双重可选 if decorationCache.keys.contains(dateComponents) { return decorationCache[dateComponents] ?? nil } let decoration = calculateDecoration(dateComponents: dateComponents) decorationCache[dateComponents] = decoration return decoration } // ... }
方案2:简化缓存逻辑,不存储nil结果
如果允许重复计算返回nil的装饰,可以将字典值类型改为非可选:
class Coordinator: NSObject, UICalendarViewDelegate, UICalendarSelectionSingleDateDelegate { // ... private var decorationCache: [DateComponents: UICalendarView.Decoration] = [:] func calendarView(_ calendarView: UICalendarView, decorationFor dateComponents: DateComponents) -> UICalendarView.Decoration? { if let decoration = decorationCache[dateComponents] { return decoration } guard let decoration = calculateDecoration(dateComponents: dateComponents) else { return nil } decorationCache[dateComponents] = decoration return decoration } // ... }
内容的提问来源于stack exchange,提问作者shinking
相关产品推荐
相关产品推荐

