SwiftUI中Slider值偶尔无法保存到UserDefaults的问题排查
问题:Slider值偶尔无法正确保存到UserDefaults的原因分析
在SwiftUI开发中,通过onEditingChanged回调将Slider的设置值保存到UserDefaults,但存在偶尔(约2-3次出现一次)保存失败的情况:比如设置fontSize为40.0、lineSpacing为20.0后重启应用,却加载到fontSize为8.0、lineSpacing为1.0。测试设备为iPhone 14 Pro Simulator,相关代码及日志如下:
相关代码
import Foundation import SwiftUI struct SliderAndUserDefaults: View { @StateObject var textOptions: TextOptions = .makeUserOptions() private let userDefault = UserDefaults.standard var body: some View { VStack { Slider( value: $textOptions.fontSize, in: 8...40, step: 1 ) { Text("font size") } minimumValueLabel: { Text("8") } maximumValueLabel: { Text("40") } onEditingChanged: { editing in if !editing { print("📝 save fontSize: \(textOptions.fontSize)") userDefault.set(textOptions.fontSize, forKey: "fontSize") } } Slider( value: $textOptions.lineSpacing, in: 1...20, step: 1 ) { Text("line spacing") } minimumValueLabel: { Text("1") } maximumValueLabel: { Text("20") } onEditingChanged: { editing in if !editing { print("📝 save lineSpacing: \(textOptions.lineSpacing)") userDefault.set(textOptions.lineSpacing, forKey: "lineSpacing") } } } } } class TextOptions: ObservableObject { @Published var fontSize: CGFloat @Published var lineSpacing: CGFloat static let defaultFontSize: CGFloat = 12 static let defaultLineSpacing: CGFloat = 4 init(fontSize: CGFloat, lineSpacing: CGFloat) { self.fontSize = fontSize self.lineSpacing = lineSpacing } static func makeUserOptions() -> TextOptions { let userDefaults = UserDefaults.standard let savedFontSize: Int = userDefaults.integer(forKey: "fontSize") let savedLineSpacing: Int = userDefaults.integer(forKey: "lineSpacing") print("📝 savedFontSize: \(savedFontSize)") print("📝 savedLineSpacing: \(savedLineSpacing)") let fontSize: CGFloat = savedFontSize == 0 ? defaultFontSize : CGFloat(savedFontSize) let lineSpacing: CGFloat = savedLineSpacing == 0 ? defaultLineSpacing : CGFloat(savedLineSpacing) print("📝 fontSize: \(fontSize)") print("📝 lineSpacing: \(lineSpacing)") return .init(fontSize: fontSize, lineSpacing: lineSpacing) } }
重启应用日志
// change slider value 📝 save fontSize: 40.0 📝 save lineSpacing: 20.0 // restart app 📝 savedFontSize: 8 📝 savedLineSpacing: 1 📝 fontSize: 8.0 📝 lineSpacing: 1.0
问题原因分析
1. UserDefaults异步写入导致的持久化延迟
UserDefaults的set(_:forKey:)方法默认是异步写入磁盘的——调用方法后,值会先存在内存缓存中,之后再后台写入磁盘。如果在写入完成前快速重启应用,内存中的新值还没同步到磁盘,重启后就会读取到之前的旧值(比如你之前设置过的8或1),这就导致了偶尔出现的保存失败。
2. 数据类型不匹配的潜在风险
代码中存在明显的类型不一致问题:
- 保存时,存入的是
CGFloat(本质是浮点数,UserDefaults会自动转成Double存储) - 读取时,用
userDefaults.integer(forKey:)强制读取成Int
虽然大多数情况下,Double(40.0)转成Int是40,但这种跨类型存储读取的做法,可能在某些边缘场景下触发UserDefaults的类型解析异常,比如内部存储的类型标识出错,导致读取到Slider的最小值(8和1)。
修复建议
统一数据类型:因为Slider的
step是1,所有值都是整数,直接用Int类型处理:- 保存时,把
textOptions.fontSize转成Int再存入UserDefaults
userDefault.set(Int(textOptions.fontSize), forKey: "fontSize")- 读取时保持原逻辑即可,因为本来就是读取
Int值后转成CGFloat
- 保存时,把
验证异步写入问题(调试用):如果想确认是否是异步写入导致的问题,可以在保存后调用
userDefault.synchronize()(苹果不推荐生产环境使用,但调试时可以快速验证):userDefault.set(Int(textOptions.fontSize), forKey: "fontSize") userDefault.synchronize()
内容的提问来源于stack exchange,提问作者user9015472
相关产品推荐
相关产品推荐

