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

SwiftUI(iOS)中保存用户设置的最优方法是什么?@AppStorage不安全,Core Data用于管理大量数据

iOS SwiftUI 下保存用户设置的最优方案

针对不同类型的用户设置,最优方案可按数据敏感程度区分:

一、非敏感用户设置(如主题偏好、语言选择、界面布局)

这类数据无需加密,优先选择贴合SwiftUI响应式特性的方案:

  • @AppStorage 属性包装器:它是SwiftUI对UserDefaults的官方封装,代码简洁且自动支持视图响应式更新,完全满足非敏感设置的需求。示例:
    @AppStorage("selectedTheme") private var selectedTheme: String = "light"
    @AppStorage("enableNotifications") private var enableNotifications: Bool = true
    
  • 直接使用 UserDefaults:如果需要批量操作或更灵活的逻辑控制,可以直接调用UserDefaults.standard,但需手动结合@ObservableObject和@Published实现响应式更新。

二、敏感用户设置(如认证令牌、隐私配置、支付相关参数)

由于@AppStorage和UserDefaults均为明文存储在沙盒plist中,存在安全风险,这类数据必须用Keychain存储。SwiftUI下推荐自定义属性包装器封装Keychain操作,兼顾安全性和响应式:

自定义 @KeychainStorage 属性包装器示例

import Foundation
import Security

@propertyWrapper
struct KeychainStorage<T: Codable> {
    private let key: String
    private let defaultValue: T

    init(key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
    }

    var wrappedValue: T {
        get {
            guard let data = KeychainHelper.read(key: key) else {
                return defaultValue
            }
            do {
                return try JSONDecoder().decode(T.self, from: data)
            } catch {
                return defaultValue
            }
        }
        set {
            do {
                let data = try JSONEncoder().encode(newValue)
                KeychainHelper.save(key: key, data: data)
            } catch {
                print("Keychain save failed: \(error.localizedDescription)")
            }
        }
    }
}

enum KeychainHelper {
    static func save(key: String, data: Data) {
        let query: [CFString: Any] = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: key,
            kSecValueData: data,
            kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked
        ]

        // 先删除旧数据再保存新数据
        SecItemDelete(query as CFDictionary)
        let status = SecItemAdd(query as CFDictionary, nil)
        guard status == errSecSuccess else {
            print("Keychain save error: \(status)")
            return
        }
    }

    static func read(key: String) -> Data? {
        let query: [CFString: Any] = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: key,
            kSecReturnData: kCFBooleanTrue!,
            kSecMatchLimit: kSecMatchLimitOne,
            kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked
        ]

        var result: AnyObject?
        let status = SecItemCopyMatching(query as CFDictionary, &result)
        guard status == errSecSuccess, let data = result as? Data else {
            return nil
        }
        return data
    }
}

使用方式

和@AppStorage完全一致,自动支持SwiftUI视图响应式更新:

@KeychainStorage(key: "userAuthToken", defaultValue: "") private var authToken: String
@KeychainStorage(key: "biometricEnabled", defaultValue: false) private var biometricEnabled: Bool

补充说明

  • 如果你的设置是结构化的、需要关联其他业务数据,但数据量不大,Core Data并非最优选择——它更适合管理大量关系型数据,小量设置用上述方案更轻量高效。
  • 若不想手动封装Keychain,也可以使用成熟的第三方库(如KeychainSwift),但自定义实现更可控,避免依赖冗余。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 08:30:42