Schwiftige und moderne Benutzerdefaults
Speichern Sie Schlüsselwertpaare anhaltend über die Starts Ihrer App.
Es verwendet UserDefaults darunter, enthält jedoch eine Typ-Sicherheit-Fassade mit vielen schönen Annehmlichkeiten.
Es wird in der Produktion von all meinen Apps (4 Millionen Benutzer) verwendet.
UserDefaults ändert.@AppStorageCodable .Toggle . Fügen Sie https://github.com/sindresorhus/Defaults in der Registerkarte "Swift Package Manager" in Xcode hinzu.
Int(8/16/32/64)UInt(8/16/32/64)DoubleCGFloatFloatStringBoolDateDataURLUUIDRangeClosedRangeCodableNSSecureCodingColor 1 (Swiftui)Color.Resolved 1 (Swiftui)NSColorUIColorNSFontDescriptorUIFontDescriptor Standardeinstellungen unterstützen auch die oben genannten Typen, die in Array , Set , Dictionary , Range , ClosedRange und sogar in verschachtelten Typen eingewickelt sind. Beispielsweise [[String: Set<[String: Int]>]] .
Weitere Typen finden Sie im Enum -Beispiel, Codable Beispiel oder im erweiterten Gebrauch. Weitere Beispiele finden Sie in Tests/Standardstests.
Sie können problemlos Unterstützung für jeden benutzerdefinierten Typ hinzufügen.
Wenn ein Typ sowohl NSSecureCoding als auch Codable entspricht, wird Codable für die Serialisierung verwendet.
API -Dokumentation.
Sie deklarieren die Standardtasten im Voraus mit einem Typ und einem Standardwert.
Der Schlüsselname muss ASCII sein, nicht mit @ beginnen und keinen Punkt ( . ) Enthalten.
import Defaults
extension Defaults . Keys {
static let quality = Key < Double > ( " quality " , default : 0.8 )
// ^ ^ ^ ^
// Key Type UserDefaults name Default value
} Sie können dann als Index für die Defaults global darauf zugreifen:
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']Sie können auch optionale Schlüssel für den Voraus deklarieren, wenn Sie keinen Standardwert deklarieren möchten:
extension Defaults . Keys {
static let name = Key < Double ? > ( " name " )
}
if let name = Defaults [ . name ] {
print ( name )
} Der Standardwert ist dann nil .
Sie können auch einen dynamischen Standardwert angeben. Dies kann nützlich sein, wenn sich der Standardwert während der Lebensdauer der App ändern kann:
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"(Dies funktioniert, solange der Rohwert des Enum eine der unterstützten Typen ist)
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" Sie müssen keine Schlüsseln an Defaults.Keys anhängen. Keys.
let isUnicorn = Defaults . Key < Bool > ( " isUnicorn " , default : true )
Defaults [ isUnicorn ]
//=> true@Default in View Sie können den @Default -Eigenschaftswrapper verwenden, um ein Defaults zu erhalten/festzulegen und die Ansicht zu aktualisieren, wenn sich der Wert ändert. Dies ähnelt @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 ( )
}
}
} Beachten Sie, dass es @Default ist, nicht @Defaults .
Sie können @Default in einem ObservableObject nicht verwenden. Es soll in einer View verwendet werden.
@ObservableDefault in @Observable Mit dem Makro @ObservableDefault können Sie Defaults in @Observable -Klassen verwenden, die das Beobachtungsframework verwenden. Dies ist so einfach wie das Importieren DefaultsMacros und das Hinzufügen von zwei Zeilen zu einer Eigenschaft (Beachten Sie, dass @ObservationIgnored addiert wird, um Zusammenstöße mit @Observable zu verhindern):
Wichtig
Die Bauzeiten steigen bei der Verwendung von Makros.
Swift-Makros hängen vom swift-syntax Paket ab. Dies bedeutet, dass Sie beim Kompilieren von Code, der Makros als Abhängigkeiten enthält, auch swift-syntax kompilieren müssen. Es ist allgemein bekannt, dass dies schwerwiegende Auswirkungen auf die Bauzeit hat, und obwohl es sich um ein Problem handelt, das verfolgt wird (siehe swift-syntax #2421), wird derzeit keine Lösung implementiert.
import Defaults
import DefaultsMacros
@ Observable
final class UnicornManager {
@ ObservableDefault ( . hasUnicorn )
@ ObservationIgnored
var hasUnicorn : Bool
} Toggle Es gibt auch einen SwiftUI.Toggle -Wrapper, der das Erstellen eines Umschusses mit einem Defaults -Taste mit einem Bool -Wert erleichtert.
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 )
}
}Sie können auch Änderungen anhören:
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 )
}
} Im Gegensatz zu der wichtigsten Beobachtung der nativen UserDefaults erhalten Sie hier ein stark typisches Änderungsobjekt.
extension Defaults . Keys {
static let isUnicornMode = Key < Bool > ( " isUnicornMode " , default : false )
}
Defaults [ . isUnicornMode ] = true
//=> true
Defaults . reset ( . isUnicornMode )
Defaults [ . isUnicornMode ]
//=> false Dies funktioniert auch für einen Key mit einem optionalen, das auf nil zurückgesetzt wird.
Änderungen, die innerhalb der Defaults.withoutPropagation vorgenommen wurden, werden nicht an Beobachtungsrückrufe ( Defaults.observe() oder Defaults.publisher() ) vorgenommen.
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 mit ZuckerDas funktioniert auch:
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 registriert Wenn UserDefaults default Defaults.Key erstellen. Dies bedeutet, dass Sie den Standardwert in den Bindungen im Schnittstellenbuilder verwenden können.
extension Defaults . Keys {
static let isUnicornMode = Key < Bool > ( " isUnicornMode " , default : true )
}
print ( UserDefaults . standard . bool ( forKey : Defaults . Keys . isUnicornMode . name ) )
//=> trueBeachten Sie, dass ein
Defaults.Keymit einem dynamischen Standardwert den Standardwert inUserDefaultsnicht registriert.
DefaultsDefaults.Keys Typ: class
Speichert die Schlüssel.
Defaults.Key (alias Defaults.Keys.Key ) Defaults . Key < T > ( _ name : String , default : T , suite : UserDefaults = . standard ) Typ: class
Erstellen Sie einen Schlüssel mit einem Standardwert.
Der Standardwert wird in die tatsächlichen UserDefaults geschrieben und kann an anderer Stelle verwendet werden. Zum Beispiel mit einer Schnittstellenbindung.
Defaults.Serializable public protocol DefaultsSerializable {
typealias Value = Bridge . Value
typealias Serializable = Bridge . Serializable
associatedtype Bridge : Defaults . Bridge
static var bridge : Bridge { get }
} Typ: protocol
Typen, die diesem Protokoll entsprechen, können mit Defaults verwendet werden.
Der Typ sollte eine statische variable bridge haben, die auf eine Instanz eines Typs verweisen sollte, der sich an Defaults.Bridge entspricht. Bridge.
Defaults.Bridge public protocol DefaultsBridge {
associatedtype Value
associatedtype Serializable
func serialize ( _ value : Value ? ) -> Serializable ?
func deserialize ( _ object : Serializable ? ) -> Value ?
} Typ: protocol
Eine Bridge ist für die Serialisierung und Deserialisierung verantwortlich.
Es verfügt über zwei zugeordnete Value und Serializable .
Value : Der Typ, den Sie verwenden möchten.Serializable : Der in UserDefaults gespeicherte Typ.serialize : Ausgestellt vor dem Speichern der UserDefaults .deserialize : Ausgestellt nach dem Abrufen des Wertes von den UserDefaults . Defaults.AnySerializable Defaults . AnySerializable < Value : Defaults . Serializable > ( _ value : Value ) Typ: class
Typ-erzeugte Wrapper für Defaults.Serializable Werte.
get<Value: Defaults.Serializable>() -> Value? : Rufen Sie den Wert ab, welcher Typ Value aus userDefaults ist.get<Value: Defaults.Serializable>(_: Value.Type) -> Value? : Geben Sie den Value an, den Sie abrufen möchten. Dies kann in einigen mehrdeutigen Fällen nützlich sein.set<Value: Defaults.Serializable>(_ newValue: Value) : Setzen Sie einen neuen Wert für Defaults.AnySerializable . Defaults.reset(keys…) Typ: func
Setzen Sie die angegebenen Schlüssel auf ihre Standardwerte zurück.
Sie können auch String -Tasten angeben, die nützlich sein können, wenn Sie einige Schlüssel in einer Sammlung speichern müssen, da es nicht möglich ist, Defaults.Key in einer Sammlung zu speichern, da sie generisch ist.
Defaults.removeAll Defaults . removeAll ( suite : UserDefaults = . standard ) Typ: func
Entfernen Sie alle Einträge aus der gegebenen UserDefaults Suite.
Defaults.withoutPropagation(_ closure:)Führen Sie den Verschluss aus, ohne Änderungsereignisse auszulösen.
Alle im Verschluss vorgenommenen Defaults werden nicht an Defaults -Ereignishörer ( Defaults.observe() und Defaults.publisher() ) ausbreitet. Dies kann nützlich sein, um eine unendliche Rekursion zu verhindern, wenn Sie einen Schlüssel im Rückruf von Änderungen für denselben Schlüssel ändern möchten.
@Default(_ key:) Holen Sie sich ein Defaults und lassen Sie die Swiftui -Ansicht aktualisiert, wenn sich der Wert ändert.
Defaults.CollectionSerializable public protocol DefaultsCollectionSerializable : Collection , Defaults . Serializable {
init ( _ elements : [ Element ] )
} Typ: protocol
Eine Collection , die in die nativen UserDefaults speichern kann.
Es sollte einen Initialisierer init(_ elements: [Element]) haben, um Defaults die De-Serialisierung durchzuführen.
Defaults.SetAlgebraSerializable public protocol DefaultsSetAlgebraSerializable : SetAlgebra , Defaults . Serializable {
func toArray ( ) -> [ Element ]
} Typ: protocol
Eine SetAlgebra , die in die nativen UserDefaults speichern kann.
Es sollte einen Funktionsfunc func toArray() -> [Element] haben, um Defaults die Serialisierung durchzuführen.
Obwohl Defaults bereits für viele Typen integriert werden, müssen Sie möglicherweise Ihren eigenen benutzerdefinierten Typ verwenden können. In der folgenden Anleitung wird angezeigt, wie Sie Ihren eigenen benutzerdefinierten Typ mit Defaults bearbeiten können.
struct User {
let name : String
let age : String
}Defaults.Bridge entspricht, die für die Behandlung von Serialisierung und Deserialisierung verantwortlich ist. 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
)
}
}User , die sich an Defaults.Serializable entspricht. Die statische Brücke sollte die Brücke sein, die wir oben geschaffen haben. 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" Es kann Situationen geben, in denen Sie [String: Any] direkt verwenden möchten, aber Defaults benötigen ihre Werte, um Defaults.Serializable zu entsprechen. Die Typ-Eraser Defaults.AnySerializable hilft diese Einschränkung.
Defaults.AnySerializable ist nur für Werte verfügbar, die den Defaults.Serializable entsprechen.
WARNUNG: Der Typ-Eraser sollte nur verwendet werden, wenn es keinen anderen Weg gibt, um damit umzugehen, da er eine viel schlechtere Leistung hat. Es sollte nur in verpackten Typen verwendet werden. Zum Beispiel in Array , Set oder Dictionary eingewickelt.
Defaults.AnySerializable entspricht ExpressibleByStringLiteral , ExpressibleByIntegerLiteral , ExpressibleByFloatLiteral , ExpressibleByBooleanLiteral , ExpressibleByNilLiteral , ExpressibleByArrayLiteral und ExpressibleByDictionaryLiteral .
Dies bedeutet, dass Sie diese primitiven Typen direkt zuweisen können:
let any = Defaults . Key < Defaults . AnySerializable > ( " anyKey " , default : 1 )
Defaults [ any ] = " ? " get und setFür andere Typen müssen Sie es so zuweisen:
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 oder Dictionary eingewickelt Defaults.AnySerializable unterstützen auch die oben genannten Typen, die in Array , Set , Dictionary eingewickelt sind.
Hier ist das Beispiel für [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"
}Weitere Beispiele finden Sie unter Tests/Standardsanyserializabletests.
Codable Typen Möglicherweise haben Sie einen Typ, der sich dem Codable & NSSecureCoding oder einer Codable & RawRepresentable Aufzählung entspricht. Standardmäßig bevorzugen Defaults die Codable Konformität und verwenden die CodableBridge , um sie in eine JSON -Zeichenfolge zu serialisieren. Wenn Sie sie als NSSecureCoding -Daten serialisieren oder den Rohwert der RawRepresentable -Enum verwenden möchten, können Sie sich an die Defaults.PreferNSSecureCoding anpassen Defaults.PreferRawRepresentable
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 application/json wir keine Defaults.PreferRawRepresentable hinzugefügt "application/json"
Dies kann auch nützlich sein, wenn Sie einen Typ, den Sie nicht Codable Defaults.Serializable steuern, anpassen. Indem Sie ausdrücklich definieren, welche Brücke verwendet werden soll, stellen Sie sicher, dass die gespeicherte Darstellung immer gleich bleibt.
CollectionCollection und lassen Sie ihre Elemente an Defaults.Serializable entsprechen. 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 ]
}
}Bag , die sich an Defaults.CollectionSerializable entspricht. 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 -TypSetAlgebra und lassen Sie ihre Elemente den 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 )
}
}SetBag , die sich an Defaults.SetAlgebraSerializable entspricht. Setalgebraserializierbar 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 Nach Defaults V5 müssen Sie keine Codable verwenden, um das Wörterbuch zu speichern. Defaults unterstützt das Speichern von Wörterbuch nativ. Für Defaults -Support -Typen finden Sie Support -Typen.
SwiftyUserDefaults ?Es ist von diesem Paket und anderen Lösungen inspiriert. Der Hauptunterschied besteht darin, dass dieses Modul die Standardwerte nicht hardcode und mit kodierbarer Unterstützung geliefert wird.
Ehemalig
Sie können Color.accentColor nicht verwenden. ↩ ↩ 2