一组针对 Foundation 的 UserDefaults
类的 Swift 仍有优化实用程序。
- 🔑 常量键 - 使用专用类型管理默认键,以帮助防止错误并保持项目组织。
- 🦺 类型安全 - 自动转换为正确类型,无需担心
Any?
。 - 🔍 观察 - Swift 中轻松实现观察。
- 👩💻 支持 Codable 和 RawRepresentable - 无需额外努力即可一致地编码和解码
Codable
和RawRepresentable
类型。 - 🧪 UI 测试中的模拟 - 将为您 UI 测试套件中的默认值直接注入到您的应用程序。
- 🎁 属性包装器 - 将 SwiftUI 的
@AppStorage
包装器功能带入 Swift,使用@UserDefault
。
在今天的 UserDefaults
中,您使用给定的 '键' 存储值。此键是 String
,随着时间的推移,使用字符串可能导致一些容易忽略的错误,除非你在某个地方定义了自己的常量。
您今天的项目可能需要进行以下操作
let userDefaults = UserDefaults.standard
var value = (userDefaults.object(forKey: "UserCount") as? Int) ?? 0
value += 1
userDefaults.set(value, forKey: "UserCoumt")
如您从上述示例中可以看到,重复使用字符串可能导致由于输入错误而产生的错误,因此一种常见的防范方法是定义常量
struct Constants {
static let userCountDefaultsKey = "UserCount"
}
// ...
let userDefaults = UserDefaults.standard
var value = (userDefaults.object(forKey: Constants.userCountDefaultsKey) as? Int) ?? 0
value += 1
userDefaults.set(value, forKey: Constants.userCountDefaultsKey)
这会更好,因为您可以放心地使用正确的键,但我们还能做得更好。
类似于 Foundation 的 Notification.Name
,SwiftUserDefaults 提供了一种新的 UserDefaults.Key
类型,它作为命名空间,为您提供自己的常量,可以方便地在您的应用程序中使用,无需担心在重构过程中可能出现的错误或问题。
import Foundation
import SwiftUserDefaults
extension UserDefaults.Key {
/// The number of users interacted with.
static let userCount = Self("UserCount")
/// The name of the user.
static let userName = Self("UserName")
/// The last visit.
static let lastVisit = Self("LastVisit")
}
SwiftUserDefaults 在此类型之上提供了一系列附加 API。继续阅读以了解如何使用它们。
当使用 UserDefaults
时,您只能尝试设置布尔值、数据、日期、数字或字符串,以及由这些类型组成的字典或数组,否则您将看到没有编译器保护的运行时崩溃。
SwiftUserDefaults 提供了更安全的 API,结合 UserDefaults.Key
提供了与 UserDefaults
相关的更安全的使用体验。
let userDefaults = UserDefaults.standard
var value = userDefaults.x.object(Int.self, forKey: .userCount) ?? 0
value += 1
userDefaults.x.set(value, forKey: .userCount)
在上面的示例中,key
参数使用 UserDefaults.Key
常量,而值自动转换为已知类型,由通过通过 x
扩展访问更安全的 API 实现。
此外,编译器可以帮助捕获在传递不支持类型到 set(_:forKey:)
时的错误。
struct User {
let id: UUID
}
func updateCurrentUser(_ user: User) {
// ❌ Runtime Crash
userDefaults.set(user.id, forKey: "UserId")
// SIGABRT
//
// Attempt to insert non-property list object
// DAE8F83E-5760-475D-B28D-D493F695E765 for key UserId
// ✅ Compile Time Error
userDefaults.x.set(user.id, forKey: .userId)
// Instance method 'set(_:forKey:)' requires that 'UUID' conform to 'UserDefaultsStorable'
}
UserDefaults
是键值观察兼容的,但是您不能使用基于 Swift key 编码的观察覆盖,因为存储的默认值没有关联到实际的属性。SwiftUserDefaults 通过围绕 Objective C 基础的 KVO 方法提供包装,从而帮助解决此问题。
import Foundation
import SwiftUserDefaults
class MyViewController: UIViewController {
let store = UserDefaults.standard
var observation: UserDefaults.Observation?
// ...
override func viewDidLoad() {
super.viewDidLoad()
// ...
observation = store.x.observeObject(String.self, forKey: .userName) { change in
self.nameLabel.text = change.value
}
}
deinit {
observation?.invalidate()
}
}
《change》属性是UserDefaults.Change
枚举,包含两个情况,分别表示.initial
值以及任何后续的.update
值。如果您不关心这些,可以通过value
属性访问底层值。
除了支持UserDefaults
的默认值类型外,还提供了一些便捷方法,方便使用Codable
和RawRepresentable
类型(包括枚举)。
对于RawRepresentable
类型,您可以像使用String
和Int
值一样使用它们,SwiftUserDefaults将自动读取和写入底层存储中的rawValue
。
enum Tab: String { // String and Int backed enum's are `RawRepresentable`.
case home, search, create
}
let initialTab = userDefaults.x.object(Tab.self, forKey: .lastTab) ?? .home
showTab(initialTab)
// ...
func tabDidChange(_ tab: Tab) {
userDefaults.x.set(tab, forKey: .lastTab)
}
对于Codable
类型,您需要传递一个额外的CodingStrategy
参数(.json
或.plist
)来指定读取和写入值时使用的编码格式。
struct Activity: Codable {
let id: UUID
let name: String
}
let restoredActivity = userDefaults.x.object(Activity.self, forKey: .currentActivity, strategy: .json)
func showActivity(_ activity: Activity) {
userDefaults.x.set(activity, forKey: .currentActivity, strategy: .json)
}
⚠️ 警告:虽然这些API可能会使您希望将大型模型编码到UserDefaults
中,但您应该继续记住,某些平台对UserDefaults
存储的大小有严格的限制。更多信息,请参阅官方Apple 开发者文档。
SwiftUserDefaults提供了一种结构化方法,可以将值从UI测试目标注入到您的应用目标的UserDefaults
中。这是通过格式化启动参数的负载来实现的,该负载是由UserDefaults
读取到NSArgumentDomain
的。
import SwiftUserDefaults
extension UserDefaults.Key {
/// The current level of the user
public static let currentLevel = Self("CurrentLevel")
/// The name of the user using the app
public static let userName = Self("UserName")
/// The unique identifier assigned to this user
public static let userGUID = Self("UserGUID")
}
import MyAppCommon
import SwiftUserDefaults
import XCTest
struct MyAppConfiguration: LaunchArgumentEncodable {
@UserDefaultOverride(.currentLevel)
var currentLevel: Int?
@UserDefaultOverride(.userName)
var userName: String?
@UserDefaultOverride(.userGUID)
var userGUID = "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF"
}
final class MyAppTests: XCTestCase {
func testMyApp() throws {
var configuration = MyAppConfiguration()
container.currentLevel = 8
container.userName = "John Doe"
let app = XCUIApplication()
app.launchArguments = try configuration.encodeLaunchArguments()
app.launch()
// ...
}
}
import SwiftUserDefaults
import UIKit
class ViewController: UIViewController {
// ...
override func viewDidLoad() {
super.viewDidLoad()
let store = UserDefaults.standard
store.x.object(Int.self, for: .currentLevel) // 8
store.x.object(String.self, for: .userName) // "John Doe"
store.x.object(String.self, for: .userGUID) // "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF"
}
}
SwiftUserDefaults将UserDefaults.Key
引入了SwiftUI的@AppStorage
属性包装器,此外,它还引入了一个具有类似行为的@UserDefault
属性包装器,适用于SwiftUI之外的使用。
使用属性包装器的最简单方法是如下所示:
import SwiftUserDefaults
class MyStore {
@UserDefault(.userName)
var userName: String?
@UserDefault(.currentLevel)
var currentLevel: Int = 1
@UserDefault(.difficulty)
var difficulty: Difficulty = .medium
}
如果您需要能够将依赖项注入到MyStore
中,您可以按如下方式操作:
import SwiftUserDefaults
class MyStore {
@UserDefault var userName: String?
@UserDefault var currentLevel: Int
@UserDefault var difficulty: Difficulty
init(userDefaults store: UserDefaults) {
_userName = UserDefault(.userName, store: store)
_currentLevel = UserDefault(.currentLevel, store: store, defaultValue: 1)
_difficulty = UserDefault(.difficulty, store: store, defaultValue: .medium)
}
}
最后,通过投影值,@UserDefault
允许您重置并观察存储的值
let store = MyStore(userDefaults: .standard)
// Removes the value from user defaults
store.$userName.reset()
// Observes the user default, respecting the default value
let observer = store.$currentLevel.addObserver { change in
change.value // Int, 1
}
与UserDefault.X
API一样,属性包装器支持原始类型、RawRepresentable
和Codable
类型。
CocoaPods是Cocoa项目的依赖项管理器。有关使用和安装说明,请访问他们的网站。要使用CocoaPods将SwiftUserDefaults集成到您的Xcode项目中,请在您的Podfile
中指定它。
pod 'swift-user-defaults'
在您的Package.swift
中添加以下内容:
dependencies: [
.package(url: "https://github.com/cookpad/swift-user-defaults.git", .upToNextMajor(from: "0.1.0"))
]
或者使用Xcode中的以下https://github.com/cookpad/swift-user-defaults.git仓库链接。