Defaults de usuario swifty y modernos
Almacene los pares de valor clave de manera persistente en los lanzamientos de su aplicación.
Utiliza UserDefaults debajo, pero expone una fachada de tipo seguro con muchas comodidades agradables.
Se usa en producción por todas mis aplicaciones (más de 4 millones de usuarios).
UserDefaults cambia.@AppStorageCodable .Toggle Swiftui de conveniencia. Agregue https://github.com/sindresorhus/Defaults en la pestaña "Swift Package Manager" en Xcode.
Int(8/16/32/64)UInt(8/16/32/64)DoubleCGFloatFloatStringBoolDateDataURLUUIDRangeClosedRangeCodableNSSecureCodingColor 1 (Swiftui)Color.Resolved 1 (Swiftui)NSColorUIColorNSFontDescriptorUIFontDescriptor Los valores predeterminados también admiten los tipos anteriores envueltos en Array , Set , Dictionary , Range , ClosedRange e incluso envueltos en tipos anidados. Por ejemplo, [[String: Set<[String: Int]>]] .
Para más tipos, consulte el ejemplo de enum, ejemplo Codable o uso avanzado. Para obtener más ejemplos, consulte las pruebas/defaultStests.
Puede agregar fácilmente soporte para cualquier tipo personalizado.
Si un tipo se ajusta tanto a NSSecureCoding como Codable , entonces se utilizará Codable para la serialización.
Documentación de API.
Declara las teclas predeterminadas por adelantado con un tipo y valor predeterminado.
El nombre clave debe ser ASCII, no comenzar con @ , y no puede contener un punto ( . ).
import Defaults
extension Defaults . Keys {
static let quality = Key < Double > ( " quality " , default : 0.8 )
// ^ ^ ^ ^
// Key Type UserDefaults name Default value
} Luego puede acceder a él como subíndice en los Defaults globales:
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']También puede declarar claves opcionales para cuando no desea declarar un valor predeterminado por adelantado:
extension Defaults . Keys {
static let name = Key < Double ? > ( " name " )
}
if let name = Defaults [ . name ] {
print ( name )
} El valor predeterminado es entonces nil .
También puede especificar un valor predeterminado dinámico. Esto puede ser útil cuando el valor predeterminado puede cambiar durante la vida útil de la aplicación:
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"(Esto funciona siempre que el valor en bruto de la enumación sea cualquiera de los tipos compatibles)
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" No está obligado a adjuntar claves a Defaults.Keys .
let isUnicorn = Defaults . Key < Bool > ( " isUnicorn " , default : true )
Defaults [ isUnicorn ]
//=> true@Default a View Puede usar el contenedor de propiedades @Default para obtener/establecer un elemento Defaults y también hacer que la vista se actualice cuando cambia el valor. Esto es similar a @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 ( )
}
}
} Tenga en cuenta que es @Default , no @Defaults .
No puede usar @Default en un ObservableObject . Está destinado a usarse en una View .
@ObservableDefault en @Observable Con la macro @ObservableDefault , puede usar Defaults dentro de las clases @Observable que usan el marco de observación. Hacerlo es tan simple como importar DefaultsMacros y agregar dos líneas a una propiedad (tenga en cuenta que se necesita agregar @ObservationIgnored para evitar enfrentamientos con @Observable ):
Importante
Los tiempos de construcción aumentarán al usar macros.
Las macros Swift dependen del paquete swift-syntax . Esto significa que cuando compila el código que incluye macros como dependencias, también debe compilar swift-syntax . Es ampliamente sabido que hacerlo tiene un impacto grave en el tiempo de construcción y, aunque es un problema que se está rastreando (ver swift-syntax #2421), actualmente no hay una solución implementada.
import Defaults
import DefaultsMacros
@ Observable
final class UnicornManager {
@ ObservableDefault ( . hasUnicorn )
@ ObservationIgnored
var hasUnicorn : Bool
} Toggle También hay un envoltorio SwiftUI.Toggle que facilita la creación de una palanca basada en una clave Defaults con un valor 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 )
}
}También puedes escuchar cambios:
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 )
}
} En contraste con la observación clave UserDefaults nativo, aquí recibe un objeto de cambio fuertemente tipado.
extension Defaults . Keys {
static let isUnicornMode = Key < Bool > ( " isUnicornMode " , default : false )
}
Defaults [ . isUnicornMode ] = true
//=> true
Defaults . reset ( . isUnicornMode )
Defaults [ . isUnicornMode ]
//=> false Esto funciona para una Key con una opcional también, que se restablecerá a nil .
Los cambios realizados dentro del Defaults.withoutPropagation Sin el cierre de propagación no se propagarán a las devoluciones de llamada de observación ( Defaults.observe() o Defaults.publisher() ) y, por lo tanto, podría evitar la recursión infinita.
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 con azúcarEsto también funciona:
extension Defaults . Keys {
static let isUnicorn = Key < Bool > ( " isUnicorn " , default : true )
}
UserDefaults . standard [ . isUnicorn ]
//=> trueUserDefaults compartidos 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 Cuando crea un Defaults.Key , registra automáticamente el valor default con UserDefaults normales. Esto significa que puede utilizar el valor predeterminado en, por ejemplo, enlaces en Interface Builder.
extension Defaults . Keys {
static let isUnicornMode = Key < Bool > ( " isUnicornMode " , default : true )
}
print ( UserDefaults . standard . bool ( forKey : Defaults . Keys . isUnicornMode . name ) )
//=> trueNota A
Defaults.Keycon un valor predeterminado dinámico no registrará el valor predeterminado enUserDefaults.
DefaultsDefaults.Keys Tipo: class
Almacena las llaves.
Defaults.Key (alias Defaults.Keys.Key ) Defaults . Key < T > ( _ name : String , default : T , suite : UserDefaults = . standard ) Tipo: class
Cree una clave con un valor predeterminado.
El valor predeterminado se escribe a los UserDefaults reales y se puede usar en otro lugar. Por ejemplo, con un enlace de constructor de interfaz.
Defaults.Serializable public protocol DefaultsSerializable {
typealias Value = Bridge . Value
typealias Serializable = Bridge . Serializable
associatedtype Bridge : Defaults . Bridge
static var bridge : Bridge { get }
} Tipo: protocol
Los tipos que se ajustan a este protocolo se pueden usar con Defaults .
El tipo debe tener un bridge variable estático que debe hacer referencia a una instancia de un tipo que se ajusta a Defaults.Bridge .
Defaults.Bridge public protocol DefaultsBridge {
associatedtype Value
associatedtype Serializable
func serialize ( _ value : Value ? ) -> Serializable ?
func deserialize ( _ object : Serializable ? ) -> Value ?
} Tipo: protocol
Un Bridge es responsable de la serialización y la deserialización.
Tiene dos tipos asociados Value y Serializable .
Value : el tipo que desea usar.Serializable : el tipo almacenado en UserDefaults .serialize : ejecutado antes de almacenar a los UserDefaults .deserialize : ejecutado después de recuperar su valor de los UserDefaults . Defaults.AnySerializable Defaults . AnySerializable < Value : Defaults . Serializable > ( _ value : Value ) Tipo: class
Wrapper en tipo de tipos para valores Defaults.Serializable . SERIALIZABLE.
get<Value: Defaults.Serializable>() -> Value? : Recupere el valor cuyo tipo es Value de UserDefaults.get<Value: Defaults.Serializable>(_: Value.Type) -> Value? : Especifique el Value que desea recuperar. Esto puede ser útil en algunos casos ambiguos.set<Value: Defaults.Serializable>(_ newValue: Value) : establecer un nuevo valor para Defaults.AnySerializable . Defaults.reset(keys…) Tipo: func
Restablezca las claves dadas a sus valores predeterminados.
También puede especificar las teclas de cadena, que pueden ser útiles si necesita almacenar algunas claves en una colección, ya que no es posible almacenar Defaults.Key Key en una colección porque es genérico.
Defaults.removeAll Defaults . removeAll ( suite : UserDefaults = . standard ) Tipo: func
Elimine todas las entradas de la suite UserDefaults dada.
Defaults.withoutPropagation(_ closure:)Ejecute el cierre sin activar eventos de cambio.
Cualquier cambio de clave Defaults realizado dentro del cierre no se propagará a los oyentes de eventos Defaults ( Defaults.observe() y Defaults.publisher() ). Esto puede ser útil para evitar una recursión infinita cuando desea cambiar una clave en la devolución de llamada que escucha los cambios para la misma clave.
@Default(_ key:) Obtenga/establezca un elemento Defaults y también se actualice la vista swiftui cuando cambia el valor.
Defaults.CollectionSerializable public protocol DefaultsCollectionSerializable : Collection , Defaults . Serializable {
init ( _ elements : [ Element ] )
} Tipo: protocol
Una Collection que puede almacenar en los UserDefaults nativos.
Debe tener un inicializador init(_ elements: [Element]) para permitir que Defaults hagan la deserialización.
Defaults.SetAlgebraSerializable public protocol DefaultsSetAlgebraSerializable : SetAlgebra , Defaults . Serializable {
func toArray ( ) -> [ Element ]
} Tipo: protocol
Un SetAlgebra que puede almacenar en los UserDefaults nativos.
Debe tener una función func toArray() -> [Element] para dejar que Defaults hagan la serialización.
Aunque Defaults ya tienen soporte incorporado para muchos tipos, es posible que deba poder usar su propio tipo personalizado. La siguiente guía le mostrará cómo hacer que su propio tipo personalizado funcione con Defaults .
struct User {
let name : String
let age : String
}Defaults.Bridge , que es responsable del manejo de la serialización y la deserialización. 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 que se ajuste a Defaults.Serializable . Su puente estático debe ser el puente que creamos arriba. 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" Puede haber situaciones en las que desee usar [String: Any] directamente, pero Defaults necesitan sus valores para conformarse con Defaults.Serializable . El tipo de trabajo de tipo Tipo Defaults.AnySerializable ayuda a superar esta limitación.
Defaults.AnySerializable solo está disponible para valores que se ajustan a Defaults.Serializable .
ADVERTENCIA: El tipo de eraser solo debe usarse cuando no hay otra forma de manejarlo porque tiene un rendimiento mucho peor. Solo debe usarse en tipos envueltos. Por ejemplo, envuelto en Array , Set o Dictionary .
Defaults.AnySerializable se ajusta a ExpressibleByStringLiteral , ExpressibleByIntegerLiteral , ExpressibleByFloatLiteral , ExpressibleByBooleanLiteral , ExpressibleByNilLiteral , ExpressibleByArrayLiteral y ExpressibleByDictionaryLiteral .
Lo que significa que puede asignar estos tipos primitivos directamente:
let any = Defaults . Key < Defaults . AnySerializable > ( " anyKey " , default : 1 )
Defaults [ any ] = " ? " get and setPara otros tipos, deberá asignarlo así:
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 o Dictionary Defaults.AnySerializable también admite los tipos anteriores envueltos en Array , Set , Dictionary .
Aquí está el ejemplo 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"
}Para obtener más ejemplos, consulte las pruebas/defaultsanySerializableLetEtests.
Codable Es posible que tenga un tipo que se ajuste a Codable & NSSecureCoding o un enum Codable & RawRepresentable . De forma predeterminada, Defaults preferirán la conformidad Codable y usará el CodableBridge para serializarlo en una cadena JSON. Si desea serializarlo como datos NSSecureCoding o utilizar el valor en bruto del enum RawRepresentable , puede ajustarse a los Defaults.PreferRawRepresentable 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 Si no hubiéramos agregado Defaults.PreferRawRepresentable , la representación almacenada habría sido "application/json" en lugar de application/json .
Esto también puede ser útil si conforma un tipo que no controla al Defaults.Serializable Serializable ya que el tipo podría recibir conformidad Codable en cualquier momento y luego la representación almacenada cambiaría, lo que podría hacer que el valor sea ilegible. Al definir explícitamente qué puente usar, se asegura de que la representación almacenada siempre permanezca igual.
Collection personalizadoCollection y haga que sus elementos se ajusten a 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 que se ajuste al 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 personalizadoSetAlgebra y haga que sus elementos se ajusten a 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 que se ajuste a 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 Después de Defaults V5, no necesita usar el diccionario Codable para almacenar el diccionario, Defaults admiten el diccionario de almacenamiento de forma nativa. Para los tipos de soporte Defaults , consulte los tipos de soporte.
SwiftyUserDefaults ?Está inspirado en ese paquete y otras soluciones. La principal diferencia es que este módulo no codifica los valores predeterminados y viene con soporte codible.
Anterior
No puede usar Color.accentColor . ↩ ↩ 2