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

在类中使用@AppStorage是否合规?如何改用UserDefaults实现视图联动?

@AppStorage在View外部使用的问题与风险
用UserDefaults实现SwiftUI视图联动的正确方式

一、@AppStorage在非View类中使用的问题与风险

@AppStorage是SwiftUI专为视图设计的属性包装器,底层基于UserDefaults实现,但直接在View外部(比如你的BiometricsAuthenticator类)使用会存在以下问题:

  • 生命周期不匹配:@AppStorage内部依赖SwiftUI视图的环境订阅机制,非View类的实例生命周期往往和视图生命周期不一致(比如单例长期驻留内存),可能导致内部订阅无法正确清理,引发内存泄漏或状态更新失效。
  • 状态流向混乱:将状态直接放在工具类中,其他视图依赖该属性时,难以追踪状态变更的触发源,大型项目中会增加维护成本,状态管理变得不透明。
  • API兼容性风险:@AppStorage是SwiftUI专属API,苹果未来可能调整其实现逻辑,比如限制仅能在View或特定SwiftUI相关类型中使用,届时你的代码可能出现兼容性问题。
  • 线程安全隐患:你在evaluatePolicy的后台回调中直接修改appBlocked,而@AppStorage未明确处理跨线程访问的线程安全问题,可能引发数据竞争,导致UI更新异常。

二、用UserDefaults实现视图联动的正确方式

核心思路是将UserDefaults的状态封装到可观察的状态管理类中,让状态变更能被SwiftUI视图感知到。以下是两种可行方案:

方案1:基于ObservableObject(兼容iOS13+)

创建一个遵循ObservableObject的状态管理类,封装UserDefaults读写逻辑,通过@Published属性触发视图更新,同时监听UserDefaults的全局变更通知以保证状态同步:

final class AppAuthState: ObservableObject {
    static let shared = AppAuthState()
    
    private let defaults = UserDefaults.standard
    private let appBlockedKey = "appBlocked"
    
    @Published var appBlocked: Bool {
        didSet {
            defaults.set(appBlocked, forKey: appBlockedKey)
        }
    }
    
    private init() {
        // 从UserDefaults初始化初始值
        appBlocked = defaults.bool(forKey: appBlockedKey)
        // 监听UserDefaults全局变更,避免外部直接修改导致状态不同步
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(updateFromDefaults),
            name: UserDefaults.didChangeNotification,
            object: nil
        )
    }
    
    @objc private func updateFromDefaults() {
        let newValue = defaults.bool(forKey: appBlockedKey)
        if newValue != appBlocked {
            appBlocked = newValue
        }
    }
    
    // 封装状态修改方法,统一入口
    func unlockApp() {
        appBlocked = false
    }
}

在BiometricsAuthenticator中使用该类:

final class BiometricsAuthenticator {
    private let authState = AppAuthState.shared
    
    func authenticate(reason: String) {
        let context = LAContext()
        context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { [weak self] success, _ in
            guard let self = self else { return }
            if success {
                // 回到主线程修改状态,保证UI更新线程安全
                DispatchQueue.main.async {
                    self.authState.unlockApp()
                }
            }
        }
    }
}

在视图中订阅状态:

struct ContentView: View {
    @ObservedObject var authState = AppAuthState.shared
    
    var body: some View {
        if authState.appBlocked {
            Text("应用已锁定")
        } else {
            Text("应用已解锁")
        }
    }
}

方案2:基于@Observable宏(iOS17+)

如果项目支持iOS17及以上,使用@Observable宏可以简化代码,替代ObservableObject和@Published:

@Observable
final class AppAuthState {
    static let shared = AppAuthState()
    
    private let defaults = UserDefaults.standard
    private let appBlockedKey = "appBlocked"
    
    var appBlocked: Bool {
        didSet {
            defaults.set(appBlocked, forKey: appBlockedKey)
        }
    }
    
    private init() {
        appBlocked = defaults.bool(forKey: appBlockedKey)
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(updateFromDefaults),
            name: UserDefaults.didChangeNotification,
            object: nil
        )
    }
    
    @objc private func updateFromDefaults() {
        let newValue = defaults.bool(forKey: appBlockedKey)
        if newValue != appBlocked {
            appBlocked = newValue
        }
    }
    
    func unlockApp() {
        appBlocked = false
    }
}

视图中使用:

struct ContentView: View {
    @State var authState = AppAuthState.shared
    
    var body: some View {
        if authState.appBlocked {
            Text("应用已锁定")
        } else {
            Text("应用已解锁")
        }
    }
}

额外注意事项

  • 线程安全:所有修改状态的操作必须在主线程执行,因为SwiftUI的视图更新依赖主线程,避免后台线程直接修改导致的异常。
  • 避免单例滥用:如果项目需要测试或多实例场景,可以通过依赖注入的方式传递AppAuthState实例,而非直接使用单例。

内容的提问来源于stack exchange,提问作者arroyot24

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 22:35:22