Swifty和Modern UserDefaults
在应用程序的启动过程中持续存储键值对。
它使用下面的UserDefaults ,但揭露了带有许多不错的便利性的类型安全外观。
我的所有应用程序(400万用户)用于生产中。
UserDefaults值更改时更新视图。@AppStorage的好处Codable 。Toggle组件。 在Xcode中的“ Swift Package Manager”选项卡中添加https://github.com/sindresorhus/Defaults 。
Int(8/16/32/64)UInt(8/16/32/64)DoubleCGFloatFloatStringBoolDateDataURLUUIDRangeClosedRangeCodableNSSecureCodingColor 1 (Swiftui)Color.Resolved分辨1 (swiftui)NSColorUIColorNSFontDescriptorUIFontDescriptor默认值还支持上述类型包裹在Array , Set , Dictionary , Range , ClosedRange中,甚至包裹在嵌套类型中。例如, [[String: Set<[String: Int]>]] 。
有关更多类型,请参见枚举示例, Codable示例或高级用法。有关更多示例,请参见测试/DEFAULTSTEST。
您可以轻松添加对任何自定义类型的支持。
如果一种类型符合NSSecureCoding和Codable ,则Codable将用于序列化。
API文档。
您可以使用类型和默认值声明默认值键。
关键名称必须是ASCII,不是以@开头,并且不能包含一个点( . )。
import Defaults
extension Defaults . Keys {
static let quality = Key < Double > ( " quality " , default : 0.8 )
// ^ ^ ^ ^
// Key Type UserDefaults name Default value
}然后,您可以在Defaults global上作为下标访问它:
Defaults [ . quality ]
//=> 0.8
Defaults [ . quality ] = 0.5
//=> 0.5
Defaults [ . quality ] += 0.1
//=> 0.6
Defaults [ . quality ] = " ? "
//=> [Cannot assign value of type 'String' to type 'Double']您还可以声明可选键,因为您不想在预先声明默认值时:
extension Defaults . Keys {
static let name = Key < Double ? > ( " name " )
}
if let name = Defaults [ . name ] {
print ( name )
}然后默认值nil 。
您还可以指定动态默认值。当默认值可能在应用程序的生命周期中发生变化时,这可能很有用:
extension Defaults . Keys {
static let camera = Key < AVCaptureDevice ? > ( " camera " ) { . default ( for : . video ) }
} enum DurationKeys : String , Defaults . Serializable {
case tenMinutes = " 10 Minutes "
case halfHour = " 30 Minutes "
case oneHour = " 1 Hour "
}
extension Defaults . Keys {
static let defaultDuration = Key < DurationKeys > ( " defaultDuration " , default : . oneHour )
}
Defaults [ . defaultDuration ] . rawValue
//=> "1 Hour"(只要枚举的原始价值是任何受支持类型的原始值)
struct User : Codable , Defaults . Serializable {
let name : String
let age : String
}
extension Defaults . Keys {
static let user = Key < User > ( " user " , default : . init ( name : " Hello " , age : " 24 " ) )
}
Defaults [ . user ] . name
//=> "Hello"您无需将密钥附加到Defaults.Keys 。
let isUnicorn = Defaults . Key < Bool > ( " isUnicorn " , default : true )
Defaults [ isUnicorn ]
//=> true@Default在View中您可以使用@Default属性包装器获取/设置Defaults项目,还可以在值更改时更新视图。这类似于@State 。
extension Defaults . Keys {
static let hasUnicorn = Key < Bool > ( " hasUnicorn " , default : false )
}
struct ContentView : View {
@ Default ( . hasUnicorn ) var hasUnicorn
var body : some View {
Text ( " Has Unicorn: ( hasUnicorn ) " )
Toggle ( " Toggle " , isOn : $hasUnicorn )
Button ( " Reset " ) {
_hasUnicorn . reset ( )
}
}
}请注意,它是@Default ,而不是@Defaults 。
您不能在ObservableObject中使用@Default 。它是在View中使用的。
@ObservableDefault in @Observable使用@ObservableDefault宏,您可以使用@Observable类中使用观察框架的Defaults 。这样做很简单,就像导入DefaultsMacros并在属性中添加两行(请注意,需要添加@ObservationIgnored来防止与@Observable发生冲突):
重要的
使用宏时,构建时间将增加。
Swift宏取决于swift-syntax软件包。这意味着,当您编译包含宏作为依赖项的代码时,您还必须编译swift-syntax 。众所周知,这样做对构建时间有严重影响,尽管这是一个正在跟踪的问题(请参阅swift-syntax #2421),但目前尚未实施解决方案。
import Defaults
import DefaultsMacros
@ Observable
final class UnicornManager {
@ ObservableDefault ( . hasUnicorn )
@ ObservationIgnored
var hasUnicorn : Bool
} Toggle还有一个SwiftUI.Toggle包装器,它使基于带有Bool值的Defaults键创建切换变得更加容易。
extension Defaults . Keys {
static let showAllDayEvents = Key < Bool > ( " showAllDayEvents " , default : false )
}
struct ShowAllDayEventsSetting : View {
var body : some View {
Defaults . Toggle ( " Show All-Day Events " , key : . showAllDayEvents )
}
}您也可以收听更改:
struct ShowAllDayEventsSetting : View {
var body : some View {
Defaults . Toggle ( " Show All-Day Events " , key : . showAllDayEvents )
// Note that this has to be directly attached to `Defaults.Toggle`. It's not `View#onChange()`.
. onChange {
print ( " Value " , $0 )
}
}
} extension Defaults . Keys {
static let isUnicornMode = Key < Bool > ( " isUnicornMode " , default : false )
}
// …
Task {
for await value in Defaults . updates ( . isUnicornMode ) {
print ( " Value: " , value )
}
}与本机UserDefaults密钥观察相反,在这里您会收到一个强大的变化对象。
extension Defaults . Keys {
static let isUnicornMode = Key < Bool > ( " isUnicornMode " , default : false )
}
Defaults [ . isUnicornMode ] = true
//=> true
Defaults . reset ( . isUnicornMode )
Defaults [ . isUnicornMode ]
//=> false这也适用于带有可选的Key ,该键将重置回nil 。
Defaults.withoutPropagation中所做的更改。没有闭合闭合将不会传播到观察回调( Defaults.observe()或Defaults.publisher() ),因此可以防止无限递归。
let observer = Defaults . observe ( keys : . key1 , . key2 ) {
// …
Defaults . withoutPropagation {
// Update `.key1` without propagating the change to listeners.
Defaults [ . key1 ] = 11
}
// This will be propagated.
Defaults [ . someKey ] = true
}UserDefaults这也有效:
extension Defaults . Keys {
static let isUnicorn = Key < Bool > ( " isUnicorn " , default : true )
}
UserDefaults . standard [ . isUnicorn ]
//=> trueUserDefaults默认 let extensionDefaults = UserDefaults ( suiteName : " com.unicorn.app " ) !
extension Defaults . Keys {
static let isUnicorn = Key < Bool > ( " isUnicorn " , default : true , suite : extensionDefaults )
}
Defaults [ . isUnicorn ]
//=> true
// Or
extensionDefaults [ . isUnicorn ]
//=> trueUserDefaults注册创建Defaults.Key时,它会自动使用普通的UserDefaults注册default值。这意味着您可以使用默认值,例如接口构建器中的绑定。
extension Defaults . Keys {
static let isUnicornMode = Key < Bool > ( " isUnicornMode " , default : true )
}
print ( UserDefaults . standard . bool ( forKey : Defaults . Keys . isUnicornMode . name ) )
//=> true请注意,具有动态默认值的
Defaults.Key将不会在UserDefaults中注册默认值。
DefaultsDefaults.Keys类型: class
存储钥匙。
Defaults.Key (alias Defaults.Keys.Key ) Defaults . Key < T > ( _ name : String , default : T , suite : UserDefaults = . standard )类型: class
创建一个具有默认值的键。
默认值写入实际的UserDefaults ,可以在其他地方使用。例如,使用接口构建器绑定。
Defaults.Serializable public protocol DefaultsSerializable {
typealias Value = Bridge . Value
typealias Serializable = Bridge . Serializable
associatedtype Bridge : Defaults . Bridge
static var bridge : Bridge { get }
}类型: protocol
符合此协议的类型可以与Defaults一起使用。
该类型应具有一个静态变量bridge ,该桥应该引用一个符合Defaults.Bridge的类型的实例。
Defaults.Bridge public protocol DefaultsBridge {
associatedtype Value
associatedtype Serializable
func serialize ( _ value : Value ? ) -> Serializable ?
func deserialize ( _ object : Serializable ? ) -> Value ?
}类型: protocol
Bridge是负责序列化和避难所化的。
它具有两个相关类型的Value , Serializable 。
Value :您要使用的类型。Serializable :存储在UserDefaults中的类型。serialize :在存储到UserDefaults之前执行。deserialize :从UserDefaults检索其值后执行。 Defaults.AnySerializable Defaults . AnySerializable < Value : Defaults . Serializable > ( _ value : Value )类型: class
基于类型的Defaults.Serializable值包装器。
get<Value: Defaults.Serializable>() -> Value? :检索哪个类型是从UserDefaults中Value 。get<Value: Defaults.Serializable>(_: Value.Type) -> Value? :指定要检索的Value 。这在某些模棱两可的情况下可能很有用。set<Value: Defaults.Serializable>(_ newValue: Value) :为Defaults.AnySerializable设置一个新值。 Defaults.reset(keys…)类型: func
将给定的键重置为默认值。
您还可以指定字符串键,如果您需要在集合中存储一些键,这很有用,因为它不可能存储Defaults.Key在集合中,因为它是通用的。
Defaults.removeAll Defaults . removeAll ( suite : UserDefaults = . standard )类型: func
从给定的UserDefaults Suite中删除所有条目。
Defaults.withoutPropagation(_ closure:)执行关闭而不触发变更事件。
闭合内进行的任何Defaults密钥更改都不会传播到Defaults事件侦听器( Defaults.observe()和Defaults.publisher() )。当您想更改回调中的键时,请聆听同一键的更改时,这对于防止无限递归很有用。
@Default(_ key:)获取/设置Defaults项目,并在值更改时更新SwiftUI视图。
Defaults.CollectionSerializable public protocol DefaultsCollectionSerializable : Collection , Defaults . Serializable {
init ( _ elements : [ Element ] )
}类型: protocol
可以存储到本机UserDefaults中的Collection 。
它应该具有一个初始化器init(_ elements: [Element])以使Defaults进行除外序列化。
Defaults.SetAlgebraSerializable public protocol DefaultsSetAlgebraSerializable : SetAlgebra , Defaults . Serializable {
func toArray ( ) -> [ Element ]
}类型: protocol
可以存储到本机UserDefaults中的SetAlgebra 。
它应该具有函数func toArray() -> [Element]以使Defaults执行序列化。
尽管Defaults已经对许多类型具有内置支持,但您可能需要能够使用自己的自定义类型。下面的指南将向您展示如何使自己的自定义类型使用Defaults 。
struct User {
let name : String
let age : String
}Defaults.Bridge的桥梁,该桥梁负责处理序列化和避难所化。 struct UserBridge : Defaults . Bridge {
typealias Value = User
typealias Serializable = [ String : String ]
public func serialize ( _ value : Value ? ) -> Serializable ? {
guard let value else {
return nil
}
return [
" name " : value . name ,
" age " : value . age
]
}
public func deserialize ( _ object : Serializable ? ) -> Value ? {
guard
let object ,
let name = object [ " name " ] ,
let age = object [ " age " ]
else {
return nil
}
return User (
name : name ,
age : age
)
}
}Defaults.Serializable的User的扩展。它的静态桥应该是我们上面创建的桥梁。 struct User {
let name : String
let age : String
}
extension User : Defaults . Serializable {
static let bridge = UserBridge ( )
} extension Defaults . Keys {
static let user = Defaults . Key < User > ( " user " , default : User ( name : " Hello " , age : " 24 " ) )
static let arrayUser = Defaults . Key < [ User ] > ( " arrayUser " , default : [ User ( name : " Hello " , age : " 24 " ) ] )
static let setUser = Defaults . Key < Set < User>> ( " user " , default : Set ( [ User ( name : " Hello " , age : " 24 " ) ] ) )
static let dictionaryUser = Defaults . Key < [ String : User ] > ( " dictionaryUser " , default : [ " user " : User ( name : " Hello " , age : " 24 " ) ] )
}
Defaults [ . user ] . name //=> "Hello"
Defaults [ . arrayUser ] [ 0 ] . name //=> "Hello"
Defaults [ . setUser ] . first ? . name //=> "Hello"
Defaults [ . dictionaryUser ] [ " user " ] ? . name //=> "Hello"在某些情况下,您要直接使用[String: Any] ,但是Defaults需要其值以符合Defaults.Serializable 。 type-eraser Defaults.AnySerializable帮助克服这一限制。
Defaults.AnySerializable仅适用于符合Defaults.Serializable的值。
警告:只有在没有其他方法来处理它的情况下,才应使用类型的eraser。它仅应在包装类型中使用。例如,包裹在Array , Set或Dictionary中。
Defaults.AnySerializable符合ExpressibleByStringLiteral , ExpressibleByIntegerLiteral , ExpressibleByFloatLiteral , ExpressibleByBooleanLiteral , ExpressibleByNilLiteral , ExpressibleByArrayLiteral和ExpressibleByDictionaryLiteral 。
这意味着您可以直接分配这些原始类型:
let any = Defaults . Key < Defaults . AnySerializable > ( " anyKey " , default : 1 )
Defaults [ any ] = " ? " get和set对于其他类型,您将必须这样分配:
enum mime : String , Defaults . Serializable {
case JSON = " application/json "
case STREAM = " application/octet-stream "
}
let any = Defaults . Key < Defaults . AnySerializable > ( " anyKey " , default : [ Defaults . AnySerializable ( mime . JSON ) ] )
if let mimeType : mime = Defaults [ any ] . get ( ) {
print ( mimeType . rawValue )
//=> "application/json"
}
Defaults [ any ] . set ( mime . STREAM )
if let mimeType : mime = Defaults [ any ] . get ( ) {
print ( mimeType . rawValue )
//=> "application/octet-stream"
} Array , Set或Dictionary Defaults.AnySerializable还支持上述类型包裹在Array , Set , Dictionary中。
这是[String: Defaults.AnySerializable]的示例:
extension Defaults . Keys {
static let magic = Key < [ String : Defaults . AnySerializable ] > ( " magic " , default : [ : ] )
}
enum mime : String , Defaults . Serializable {
case JSON = " application/json "
}
// …
Defaults [ . magic ] [ " unicorn " ] = " ? "
if let value : String = Defaults [ . magic ] [ " unicorn " ] ? . get ( ) {
print ( value )
//=> "?"
}
Defaults [ . magic ] [ " number " ] = 3
Defaults [ . magic ] [ " boolean " ] = true
Defaults [ . magic ] [ " enum " ] = Defaults . AnySerializable ( mime . JSON )
if let mimeType : mime = Defaults [ . magic ] [ " enum " ] ? . get ( ) {
print ( mimeType . rawValue )
//=> "application/json"
}有关更多示例,请参见测试/默认情况下的序列化。
Codable类型的序列化您可能有一种符合Codable & NSSecureCoding或Codable & RawRepresentable枚举的类型。默认情况下, Defaults将更喜欢Codable符合度,并使用CodableBridge桥将其序列化为JSON字符串。 Defaults.PreferRawRepresentable要将其序列化为NSSecureCoding数据或使用RawRepresentable ENUM的原始值,则可以符合Defaults.PreferNSSecureCoding 。
enum mime : String , Codable , Defaults . Serializable , Defaults . PreferRawRepresentable {
case JSON = " application/json "
}
extension Defaults . Keys {
static let magic = Key < [ String : Defaults . AnySerializable ] > ( " magic " , default : [ : ] )
}
print ( UserDefaults . standard . string ( forKey : " magic " ) )
//=> application/json如果我们没有添加Defaults.PreferRawRepresentable ,存储的表示形式将是"application/json"而不是application/json 。
如果您符合您无法控制Defaults.Serializable类型的类型,这也可能很有用。由于类型可以随时接收Codable符合性,然后存储的表示形式将会更改,这可能会使值不可读。通过明确定义要使用的桥梁,您可以确保存储的表示形式始终保持不变。
Collection类型Collection并使其元素符合Defaults.Serializable 。 struct Bag < Element : Defaults . Serializable > : Collection {
var items : [ Element ]
var startIndex : Int { items . startIndex }
var endIndex : Int { items . endIndex }
mutating func insert ( element : Element , at : Int ) {
items . insert ( element , at : at )
}
func index ( after index : Int ) -> Int {
items . index ( after : index )
}
subscript ( position : Int ) -> Element {
items [ position ]
}
}Defaults.CollectionSerializable的Bag的扩展名。 extension Bag : Defaults . CollectionSerializable {
init ( _ elements : [ Element ] ) {
self . items = elements
}
} extension Defaults . Keys {
static let stringBag = Key < Bag < String > > ( " stringBag " , default : Bag ( [ " Hello " , " World! " ] ) )
}
Defaults [ . stringBag ] [ 0 ] //=> "Hello"
Defaults [ . stringBag ] [ 1 ] //=> "World!"SetAlgebra类型SetAlgebra并使其元素符合Defaults.Serializable & Hashable struct SetBag < Element : Defaults . Serializable & Hashable > : SetAlgebra {
var store = Set < Element > ( )
init ( ) { }
init ( _ store : Set < Element > ) {
self . store = store
}
func contains ( _ member : Element ) -> Bool {
store . contains ( member )
}
func union ( _ other : SetBag ) -> SetBag {
SetBag ( store . union ( other . store ) )
}
func intersection ( _ other : SetBag ) -> SetBag {
var setBag = SetBag ( )
setBag . store = store . intersection ( other . store )
return setBag
}
func symmetricDifference ( _ other : SetBag ) -> SetBag {
var setBag = SetBag ( )
setBag . store = store . symmetricDifference ( other . store )
return setBag
}
@ discardableResult
mutating func insert ( _ newMember : Element ) -> ( inserted : Bool , memberAfterInsert : Element ) {
store . insert ( newMember )
}
mutating func remove ( _ member : Element ) -> Element ? {
store . remove ( member )
}
mutating func update ( with newMember : Element ) -> Element ? {
store . update ( with : newMember )
}
mutating func formUnion ( _ other : SetBag ) {
store . formUnion ( other . store )
}
mutating func formSymmetricDifference ( _ other : SetBag ) {
store . formSymmetricDifference ( other . store )
}
mutating func formIntersection ( _ other : SetBag ) {
store . formIntersection ( other . store )
}
}Defaults.SetAlgebraSerializable的SetBag的扩展 extension SetBag : Defaults . SetAlgebraSerializable {
func toArray ( ) -> [ Element ] {
Array ( store )
}
} extension Defaults . Keys {
static let stringSet = Key < SetBag < String > > ( " stringSet " , default : SetBag ( [ " Hello " , " World! " ] ) )
}
Defaults [ . stringSet ] . contains ( " Hello " ) //=> true
Defaults [ . stringSet ] . contains ( " World! " ) //=> true在Defaults V5之后,您无需使用Codable来存储字典, Defaults支持本机上存储字典。对于Defaults支持类型,请参见支持类型。
SwiftyUserDefaults有何不同?它受到该软件包和其他解决方案的启发。主要区别在于,该模块没有编码默认值,并带有代码支持。
以前的
您不能使用Color.accentColor 。 ↩2