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的原始值,則可以符合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