Swifty et Modern UserDefaults
Stockez les paires de valeurs clés de manière persistante à travers les lancements de votre application.
Il utilise UserDefaults en dessous mais expose une façade de type type avec beaucoup de bonnes commodités.
Il est utilisé dans la production par toutes mes applications (4 millions + utilisateurs).
UserDefaults change.@AppStorageCodable .Toggle Swiftui commodité. Ajoutez https://github.com/sindresorhus/Defaults dans l'onglet "Swift Package Manager" dans Xcode.
Int(8/16/32/64)UInt(8/16/32/64)DoubleCGFloatFloatStringBoolDateDataURLUUIDRangeClosedRangeCodableNSSecureCodingColor 1 (Swiftui)Color.Resolved 1 (swiftui)NSColorUIColorNSFontDescriptorUIFontDescriptor Les valeurs par défaut prennent également en charge les types ci-dessus enveloppés dans Array , Set , Dictionary , Range , ClosedRange et même enveloppés dans des types imbriqués. Par exemple, [[String: Set<[String: Int]>]] .
Pour plus de types, consultez l'exemple d'énumération, l'exemple Codable ou l'utilisation avancée. Pour plus d'exemples, voir Tests / DefaultStstss.
Vous pouvez facilement ajouter la prise en charge de tout type personnalisé.
Si un type est conforme à la fois à NSSecureCoding et Codable , Codable sera utilisé pour la sérialisation.
Documentation de l'API.
Vous déclarez les clés par défaut à l'avance avec un type et une valeur par défaut.
Le nom de clé doit être ASCII, pas commencer par @ et ne peut pas contenir de point ( . ).
import Defaults
extension Defaults . Keys {
static let quality = Key < Double > ( " quality " , default : 0.8 )
// ^ ^ ^ ^
// Key Type UserDefaults name Default value
} Vous pouvez ensuite y accéder en tant qu'indice sur les Defaults globaux:
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']Vous pouvez également déclarer des clés facultatives lorsque vous ne souhaitez pas déclarer une valeur par défaut à l'avance:
extension Defaults . Keys {
static let name = Key < Double ? > ( " name " )
}
if let name = Defaults [ . name ] {
print ( name )
} La valeur par défaut est alors nil .
Vous pouvez également spécifier une valeur par défaut dynamique. Cela peut être utile lorsque la valeur par défaut peut changer pendant la durée de vie de l'application:
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"(Cela fonctionne tant que la valeur brute de l'énumération est l'un des types pris en charge)
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" Vous n'êtes pas obligé d'attacher des clés à Defaults.Keys .
let isUnicorn = Defaults . Key < Bool > ( " isUnicorn " , default : true )
Defaults [ isUnicorn ]
//=> true@Default en View Vous pouvez utiliser l'emballage de la propriété @Default pour obtenir / définir un élément Defaults et également que la vue soit mise à jour lorsque la valeur change. Ceci est similaire à @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 ( )
}
}
} Notez que c'est @Default , pas @Defaults .
Vous ne pouvez pas utiliser @Default dans un ObservableObject . Il est censé être utilisé dans une View .
@ObservableDefault dans @Observable Avec la macro @ObservableDefault , vous pouvez utiliser Defaults à l'intérieur des classes @Observable qui utilisent le cadre d'observation. Cela est aussi simple que l'importation DefaultsMacros et l'ajout de deux lignes à une propriété (notez que l'ajout @ObservationIgnored est nécessaire pour empêcher les affrontements avec @Observable ):
Important
Les temps de construction augmenteront lors de l'utilisation de macros.
Les macros Swift dépendent du package swift-syntax . Cela signifie que lorsque vous compilez du code qui inclut les macros en tant que dépendances, vous devez également compiler swift-syntax . Il est largement connu que cela a un impact sérieux dans le temps de construction et, bien qu'il s'agisse d'un problème qui est suivi (voir swift-syntax # 2421), il n'y a actuellement aucune solution implémentée.
import Defaults
import DefaultsMacros
@ Observable
final class UnicornManager {
@ ObservableDefault ( . hasUnicorn )
@ ObservationIgnored
var hasUnicorn : Bool
} Toggle Il y a aussi un wrapper SwiftUI.Toggle qui facilite la création d'une bascule basée sur une clé Defaults avec une valeur Bool .
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 )
}
}Vous pouvez également écouter les changements:
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 )
}
} Contrairement à l'observation clé UserDefaults natif, vous recevez ici un objet de changement fortement typlé.
extension Defaults . Keys {
static let isUnicornMode = Key < Bool > ( " isUnicornMode " , default : false )
}
Defaults [ . isUnicornMode ] = true
//=> true
Defaults . reset ( . isUnicornMode )
Defaults [ . isUnicornMode ]
//=> false Cela fonctionne également pour une Key avec une facultation, qui sera réinitialisée à nil .
Les modifications apportées dans les Defaults.withoutPropagation avec la fermeture de la propagation, ne seront pas propagées aux rappels d'observation ( Defaults.observe() ou Defaults.publisher() ), et pourraient donc empêcher la récursivité infinie.
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 avec du sucreCela fonctionne aussi:
extension Defaults . Keys {
static let isUnicorn = Key < Bool > ( " isUnicorn " , default : true )
}
UserDefaults . standard [ . isUnicorn ]
//=> trueUserDefaults partagés 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 Lorsque vous créez un Defaults.Key , il enregistre automatiquement la valeur default avec UserDefaults normaux. Cela signifie que vous pouvez utiliser la valeur par défaut, par exemple, les liaisons dans Interface Builder.
extension Defaults . Keys {
static let isUnicornMode = Key < Bool > ( " isUnicornMode " , default : true )
}
print ( UserDefaults . standard . bool ( forKey : Defaults . Keys . isUnicornMode . name ) )
//=> trueRemarque un
Defaults.Keyavec une valeur par défaut dynamique n'enregistrera pas la valeur par défaut dansUserDefaults.
DefaultsDefaults.Keys Type: class
Stocke les clés.
Defaults.Key (alias Defaults.Keys.Key ) Defaults . Key < T > ( _ name : String , default : T , suite : UserDefaults = . standard ) Type: class
Créez une clé avec une valeur par défaut.
La valeur par défaut est écrite dans les UserDefaults réels et peut être utilisée ailleurs. Par exemple, avec une liaison du constructeur d'interface.
Defaults.Serializable public protocol DefaultsSerializable {
typealias Value = Bridge . Value
typealias Serializable = Bridge . Serializable
associatedtype Bridge : Defaults . Bridge
static var bridge : Bridge { get }
} Type: protocol
Les types conformes à ce protocole peuvent être utilisés avec Defaults .
Le type doit avoir un bridge variable statique qui doit faire référence à une instance d'un type conforme à Defaults.Bridge .
Defaults.Bridge public protocol DefaultsBridge {
associatedtype Value
associatedtype Serializable
func serialize ( _ value : Value ? ) -> Serializable ?
func deserialize ( _ object : Serializable ? ) -> Value ?
} Type: protocol
Un Bridge est responsable de la sérialisation et de la désérialisation.
Il a deux types associés Value et Serializable .
Value : le type que vous souhaitez utiliser.Serializable : le type stocké dans UserDefaults .serialize : exécuté avant de stocker vers UserDefaults .deserialize : exécuté après récupération de sa valeur à partir de UserDefaults . Defaults.AnySerializable Defaults . AnySerializable < Value : Defaults . Serializable > ( _ value : Value ) Type: class
Wrapper de type pour les valeurs Defaults.Serializable .
get<Value: Defaults.Serializable>() -> Value? : Récupérez la valeur de la Value du type de userDefaults.get<Value: Defaults.Serializable>(_: Value.Type) -> Value? : Spécifiez la Value que vous souhaitez récupérer. Cela peut être utile dans certains cas ambigus.set<Value: Defaults.Serializable>(_ newValue: Value) : Définissez une nouvelle valeur pour Defaults.AnySerializable . Defaults.reset(keys…) Type: func
Réinitialisez les touches données à leurs valeurs par défaut.
Vous pouvez également spécifier des touches de chaîne, qui peuvent être utiles si vous devez stocker des clés dans une collection, car il n'est pas possible de stocker Defaults.Key dans une collection car elle est générique.
Defaults.removeAll Defaults . removeAll ( suite : UserDefaults = . standard ) Type: func
Retirez toutes les entrées de la suite UserDefaults donnée.
Defaults.withoutPropagation(_ closure:)Exécutez la fermeture sans déclencher d'événements de changement.
Toutes les modifications clés Defaults apportées dans la fermeture ne se propageront pas aux auditeurs d'événements Defaults ( Defaults.observe() et Defaults.publisher() ). Cela peut être utile pour empêcher la récursivité infinie lorsque vous souhaitez modifier une clé dans l'écoute de rappel des modifications pour la même clé.
@Default(_ key:) Obtenez / définissez un élément Defaults et demandez également à la vue Swiftui soit mise à jour lorsque la valeur change.
Defaults.CollectionSerializable public protocol DefaultsCollectionSerializable : Collection , Defaults . Serializable {
init ( _ elements : [ Element ] )
} Type: protocol
Une Collection qui peut stocker dans les UserDefaults natifs.
Il devrait avoir un initialiseur init(_ elements: [Element]) pour permettre Defaults de faire la dérérialisation.
Defaults.SetAlgebraSerializable public protocol DefaultsSetAlgebraSerializable : SetAlgebra , Defaults . Serializable {
func toArray ( ) -> [ Element ]
} Type: protocol
Un SetAlgebra qui peut stocker dans les UserDefaults natifs.
Il devrait avoir une fonction func toArray() -> [Element] pour permettre Defaults de faire la sérialisation.
Bien que Defaults aient déjà une prise en charge intégrée pour de nombreux types, vous devrez peut-être pouvoir utiliser votre propre type personnalisé. Le guide ci-dessous vous montrera comment faire fonctionner votre propre type personnalisé avec Defaults .
struct User {
let name : String
let age : String
}Defaults.Bridge , qui est responsable de la gestion de la sérialisation et de la désérialisation. 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 conforme aux Defaults.Serializable . Son pont statique devrait être le pont que nous avons créé ci-dessus. 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" Il peut y avoir des situations où vous souhaitez utiliser [String: Any] directement, mais Defaults ont besoin de ses valeurs pour se conformer aux Defaults.Serializable . Le type de type par Defaults.AnySerializable aide à surmonter cette limitation.
Defaults.AnySerializable sont uniquement disponibles pour les valeurs conformes aux Defaults.Serializable .
AVERTISSEMENT: Le type de type ne doit être utilisé que lorsqu'il n'y a pas d'autre moyen de le gérer car il a des performances bien pires. Il ne doit être utilisé que dans des types enveloppés. Par exemple, enveloppé dans Array , Set ou Dictionary .
Defaults.AnySerializable se conforme à ExpressibleByStringLiteral , ExpressibleByIntegerLiteral , ExpressibleByFloatLiteral , ExpressibleByBooleanLiteral , ExpressibleByNilLiteral , ExpressibleByArrayLiteral et ExpressibleByDictionaryLiteral .
Ce qui signifie que vous pouvez attribuer directement ces types primitifs:
let any = Defaults . Key < Defaults . AnySerializable > ( " anyKey " , default : 1 )
Defaults [ any ] = " ? " get and setPour d'autres types, vous devrez l'attribuer comme suit:
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 ou Dictionary Defaults.AnySerializable prennent également en charge les types ci-dessus enveloppés dans Array , Set , Dictionary .
Voici l'exemple de [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"
}Pour plus d'exemples, consultez Tests / DefaultSanySerializableTests.
Codable ambigu Vous pouvez avoir un type qui est conforme à Codable & NSSecureCoding ou à une énumération Codable & RawRepresentable . Par défaut, Defaults préféreront la conformité Codable et utiliseront le CodableBridge pour le sérialiser dans une chaîne JSON. Si vous souhaitez le sérialiser en tant que données NSSecureCoding ou utiliser la valeur brute de l'énumération RawRepresentable , vous pouvez vous conformer aux Defaults.PreferNSSecureCoding 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 Si nous n'avions pas ajouté Defaults.PreferRawRepresentable application/json "application/json"
Cela peut également être utile si vous conformez un type que vous ne contrôlez pas aux Defaults.Serializable Sérialisable car le type pourrait recevoir une conformité Codable à tout moment, puis la représentation stockée changerait, ce qui pourrait rendre la valeur illisible. En définissant explicitement le pont à utiliser, vous vous assurez que la représentation stockée restera toujours la même.
Collection personnaliséCollection et faites en sorte que ses éléments sont conformes aux 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 ]
}
}Bag conforme aux Defaults.CollectionSerializable . 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 personnaliséSetAlgebra et faites en sorte que ses éléments se conforment aux 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 conforme aux Defaults.SetAlgebraSerializable 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 Après Defaults v5, vous n'avez pas besoin d'utiliser Codable pour stocker le dictionnaire, Defaults prend en charge le stockage de dictionnaire nativement. Pour les types de supports Defaults , voir les types de support.
SwiftyUserDefaults ?Il est inspiré par ce package et d'autres solutions. La principale différence est que ce module ne cache pas les valeurs par défaut et est livré avec une prise en charge codable.
Ancien
Vous ne pouvez pas utiliser Color.accentColor . ↩ ↩ 2