Swift中向UserDefaults存储非Codable结构体数组崩溃问题
问题:非Codable结构体数组存入UserDefaults崩溃
尝试将不兼容Codable的HabitConfiguration结构体数组存入UserDefaults,先转字典再存储,编译无错误但运行时崩溃。
待存储的结构体模型(非Codable)
struct HabitConfiguration { var name: String = "" var description: String = "" var appearance: String = "planet" var planetColor: Color = Color("PlanetColor1") var planetHasRing: Bool = false var planetRingStyle = PlanetRingStyle() var planetRotation: Double = 0 var emoji: String = "🚦" var repetitionType: RepetitionType = .unique(date: [UniqueDate(day: 1, month: 1, time: Time(hours: 12, minutes: 0))]) }
关联依赖代码
struct PlanetRingStyle { var lineWidth: Double = 5 var lineCap: CGLineCap = .round var dash: [CGFloat] = [1] } enum RepetitionType { case daily(time: Time) case weekly(days: Days, time: Time) case monthly(dates: DaysNumbers, time: Time) case unique(date: [UniqueDate]) } extension RepetitionType { var isDaily: Bool { if case .daily = self { return true } return false } var isWeekly: Bool { if case .weekly = self { return true } return false } var isMonthly: Bool { if case .monthly = self { return true } return false } var isUnique: Bool { if case .unique = self { return true } return false } }
当前字典转换实现(存在问题)
extension HabitConfiguration { func toDictionary() -> [String: Any] { return [ "name": name, "description": description, "appearance": appearance, "planetColor": planetColor.description, "planetHasRing": planetHasRing, "planetRingStyle": planetRingStyle, "planetRotation": planetRotation, "emoji": emoji, "repetitionType": repetitionType ] } init?(from dictionary: [String: Any]) { guard let name = dictionary["name"] as? String, let description = dictionary["description"] as? String, let appearance = dictionary["appearance"] as? String, let planetColorDescription = dictionary["planetColor"] as? String, let planetHasRing = dictionary["planetHasRing"] as? Bool, let planetRingStyle = dictionary["planetRingStyle"] as? Int, let planetRotation = dictionary["planetRotation"] as? Double, let emoji = dictionary["emoji"] as? String, let repetitionType = dictionary["repetitionType"] as? RepetitionType else { return nil } self.name = name self.description = description self.appearance = appearance self.planetColor = Color(planetColorDescription) self.planetHasRing = planetHasRing self.planetRingStyle = PlanetRingStyle(lineWidth: 0, lineCap: .round, dash: [0]) self.planetRotation = planetRotation self.emoji = emoji self.repetitionType = repetitionType } }
UserDefaults读写代码
struct HabitsList { private let habitsKey = "habitsList" func loadHabits() -> [HabitConfiguration] { guard let savedHabits = UserDefaults.standard.array(forKey: habitsKey) as? [[String: Any]] else { return [] } return savedHabits.compactMap { HabitConfiguration(from: $0) } } func saveHabits(_ habits: [HabitConfiguration]) { let habitDictionaries = habits.map { $0.toDictionary() } UserDefaults.standard.set(habitDictionaries, forKey: habitsKey) } func habitsList() -> Binding<[HabitConfiguration]> { Binding( get: { self.loadHabits() }, set: { self.saveHabits($0) } ) } }
崩溃原因及修复方案
核心问题
UserDefaults仅支持存储属性列表类型(NSString、NSNumber、NSDate、NSData、NSArray、NSDictionary),当前代码直接将PlanetRingStyle、RepetitionType这类自定义结构体/枚举存入字典,无法被UserDefaults序列化,导致运行时崩溃;同时字典转结构体的代码存在类型转换错误(比如把PlanetRingStyle强制转成Int)。
分步修复
1. 为自定义类型实现属性列表兼容的序列化/反序列化
处理PlanetRingStyle
给结构体添加字典转换方法,将所有属性转为UserDefaults支持的类型:
extension PlanetRingStyle { func toDictionary() -> [String: Any] { return [ "lineWidth": lineWidth, "lineCap": lineCap.rawValue, // CGLineCap的rawValue为Int类型 "dash": dash.map { $0 as Double } // 转Double避免CGFloat兼容性问题 ] } init?(from dictionary: [String: Any]) { guard let lineWidth = dictionary["lineWidth"] as? Double, let lineCapRaw = dictionary["lineCap"] as? Int, let dashValues = dictionary["dash"] as? [Double] else { return nil } self.lineWidth = lineWidth self.lineCap = CGLineCap(rawValue: lineCapRaw) ?? .round self.dash = dashValues.map { CGFloat($0) } } }
处理RepetitionType
枚举需转为包含类型标识和关联值的字典再存储:
extension RepetitionType { func toDictionary() -> [String: Any] { switch self { case .daily(let time): return ["type": "daily", "time": time.toDictionary()] case .weekly(let days, let time): return ["type": "weekly", "days": days.toDictionary(), "time": time.toDictionary()] case .monthly(let dates, let time): return ["type": "monthly", "dates": dates.toDictionary(), "time": time.toDictionary()] case .unique(let dates): return ["type": "unique", "dates": dates.map { $0.toDictionary() }] } } init?(from dictionary: [String: Any]) { guard let type = dictionary["type"] as? String else { return nil } switch type { case "daily": guard let timeDict = dictionary["time"] as? [String: Any], let time = Time(from: timeDict) else { return nil } self = .daily(time: time) case "weekly": guard let daysDict = dictionary["days"] as? [String: Any], let days = Days(from: daysDict), let timeDict = dictionary["time"] as? [String: Any], let time = Time(from: timeDict) else { return nil } self = .weekly(days: days, time: time) case "monthly": guard let datesDict = dictionary["dates"] as? [String: Any], let dates = DaysNumbers(from: datesDict), let timeDict = dictionary["time"] as? [String: Any], let time = Time(from: timeDict) else { return nil } self = .monthly(dates: dates, time: time) case "unique": guard let dateDicts = dictionary["dates"] as? [[String: Any]] else { return nil } let dates = dateDicts.compactMap { UniqueDate(from: $0) } guard !dates.isEmpty else { return nil } self = .unique(date: dates) default: return nil } } }
注意:需给
Time、Days、DaysNumbers、UniqueDate这些关联类型也添加对应的toDictionary()和init?(from:)方法,确保它们能转为属性列表类型。
2. 修正HabitConfiguration的字典转换
extension HabitConfiguration { func toDictionary() -> [String: Any] { return [ "name": name, "description": description, "appearance": appearance, "planetColor": planetColor.description, "planetHasRing": planetHasRing, "planetRingStyle": planetRingStyle.toDictionary(), // 转为兼容字典 "planetRotation": planetRotation, "emoji": emoji, "repetitionType": repetitionType.toDictionary() // 转为兼容字典 ] } init?(from dictionary: [String: Any]) { guard let name = dictionary["name"] as? String, let description = dictionary["description"] as? String, let appearance = dictionary["appearance"] as? String, let planetColorDescription = dictionary["planetColor"] as? String, let planetHasRing = dictionary["planetHasRing"] as? Bool, let ringStyleDict = dictionary["planetRingStyle"] as? [String: Any], let ringStyle = PlanetRingStyle(from: ringStyleDict), let planetRotation = dictionary["planetRotation"] as? Double, let emoji = dictionary["emoji"] as? String, let repetitionTypeDict = dictionary["repetitionType"] as? [String: Any], let repetitionType = RepetitionType(from: repetitionTypeDict) else { return nil } self.name = name self.description = description self.appearance = appearance self.planetColor = Color(planetColorDescription) self.planetHasRing = planetHasRing self.planetRingStyle = ringStyle // 使用反序列化后的对象 self.planetRotation = planetRotation self.emoji = emoji self.repetitionType = repetitionType } }
3. 验证存储
现在habitDictionaries中的所有值均为属性列表兼容类型,可安全存入UserDefaults,不会再出现崩溃问题。
内容的提问来源于stack exchange,提问作者ldt
相关产品推荐
相关产品推荐

