Swifty e Modern UserDefaults
Armazene os pares de valores-chave persistentemente nos lançamentos do seu aplicativo.
Ele usa UserDefaults abaixo, mas expõe uma fachada segura por tipo com muitas conveniências agradáveis.
É usado na produção por todos os meus aplicativos (4 milhões de usuários).
UserDefaults muda.@AppStorageCodable .Toggle Component. Adicione https://github.com/sindresorhus/Defaults na guia "Swift Package Manager" no Xcode.
Int(8/16/32/64)UInt(8/16/32/64)DoubleCGFloatFloatStringBoolDateDataURLUUIDRangeClosedRangeCodableNSSecureCodingColor 1 (Swiftui)Color.Resolved 1 (Swiftui)NSColorUIColorNSFontDescriptorUIFontDescriptor Os padrões também suportam os tipos acima envolvidos em Array , Set , Dictionary , Range , ClosedRange e até embrulhados em tipos aninhados. Por exemplo, [[String: Set<[String: Int]>]] .
Para mais tipos, consulte o exemplo enum, exemplo Codable ou uso avançado. Para mais exemplos, consulte Testes/Defaultstests.
Você pode adicionar facilmente suporte para qualquer tipo personalizado.
Se um tipo estiver em conformidade com NSSecureCoding e Codable , Codable será usado para a serialização.
Documentação da API.
Você declara as chaves padrão com um tipo de tipo e padrão.
O nome da chave deve ser ASCII, não começar com @ e não pode conter um ponto ( . ).
import Defaults
extension Defaults . Keys {
static let quality = Key < Double > ( " quality " , default : 0.8 )
// ^ ^ ^ ^
// Key Type UserDefaults name Default value
} Você pode acessá -lo como um subscrito no 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']Você também pode declarar chaves opcionais para quando não deseja declarar um valor padrão antecipadamente:
extension Defaults . Keys {
static let name = Key < Double ? > ( " name " )
}
if let name = Defaults [ . name ] {
print ( name )
} O valor padrão é então nil .
Você também pode especificar um valor padrão dinâmico. Isso pode ser útil quando o valor padrão pode mudar durante a vida útil do aplicativo:
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"(Isso funciona desde que o valor bruto da enumeração seja qualquer um dos tipos suportados)
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" Você não é obrigado a anexar chaves ao Defaults.Keys .
let isUnicorn = Defaults . Key < Bool > ( " isUnicorn " , default : true )
Defaults [ isUnicorn ]
//=> true@Default em View Você pode usar o wrapper @Default Property para obter/definir um item Defaults e também fazer com que a visualização seja atualizada quando o valor for alterado. Isso é semelhante ao @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 ( )
}
}
} Observe que é @Default , não @Defaults .
Você não pode usar @Default em um ObservableObject . É para ser usado em uma View .
@ObservableDefault em @Observable Com a macro @ObservableDefault , você pode usar Defaults nas classes @Observable que usam a estrutura de observação. Fazer isso é tão simples quanto importar DefaultsMacros e adicionar duas linhas a uma propriedade (observe que é necessário adicionar @ObservationIgnored para evitar confrontos com @Observable ):
Importante
Os tempos de construção aumentarão ao usar macros.
As macros rápidas dependem do pacote swift-syntax . Isso significa que, quando você compila o código que inclui macros como dependências, você também precisa compilar swift-syntax . Sabe-se amplamente que isso tem um impacto sério no tempo de construção e, embora seja um problema que esteja sendo rastreado (consulte swift-syntax #2421), atualmente não há nenhuma solução implementada.
import Defaults
import DefaultsMacros
@ Observable
final class UnicornManager {
@ ObservableDefault ( . hasUnicorn )
@ ObservationIgnored
var hasUnicorn : Bool
} Toggle Há também um invólucro SwiftUI.Toggle que facilita a criação de uma alternância com base em uma tecla Defaults com um 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 )
}
}Você também pode ouvir as mudanças:
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 )
}
} Em contraste com a observação de chaves UserDefaults nativo, aqui você recebe um objeto de alteração fortemente tado.
extension Defaults . Keys {
static let isUnicornMode = Key < Bool > ( " isUnicornMode " , default : false )
}
Defaults [ . isUnicornMode ] = true
//=> true
Defaults . reset ( . isUnicornMode )
Defaults [ . isUnicornMode ]
//=> false Isso também funciona para uma Key com um opcional, que será redefinido de volta para nil .
As alterações feitas no Defaults.withoutPropagation Com o fechamento da propuração não serão propagadas para observar retornos de chamada ( Defaults.observe() ou Defaults.publisher() ) e, portanto, podem impedir a recursão 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 com açúcarIsso também funciona:
extension Defaults . Keys {
static let isUnicorn = Key < Bool > ( " isUnicorn " , default : true )
}
UserDefaults . standard [ . isUnicorn ]
//=> trueUserDefaults compartilhados 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 UserDefaults você cria default Defaults.Key . Isso significa que você pode usar o valor padrão, por exemplo, encadernas no construtor de interface.
extension Defaults . Keys {
static let isUnicornMode = Key < Bool > ( " isUnicornMode " , default : true )
}
print ( UserDefaults . standard . bool ( forKey : Defaults . Keys . isUnicornMode . name ) )
//=> trueNOTA A
Defaults.KeyUserDefaults
DefaultsDefaults.Keys Tipo: class
Armazena as chaves.
Defaults.Key (alias Defaults.Keys.Key ) Defaults . Key < T > ( _ name : String , default : T , suite : UserDefaults = . standard ) Tipo: class
Crie uma chave com um valor padrão.
O valor padrão é gravado nos UserDefaults real e pode ser usado em outros lugares. Por exemplo, com uma ligação do construtor de interface.
Defaults.Serializable public protocol DefaultsSerializable {
typealias Value = Bridge . Value
typealias Serializable = Bridge . Serializable
associatedtype Bridge : Defaults . Bridge
static var bridge : Bridge { get }
} Tipo: protocol
Os tipos que estão em conformidade com este protocolo podem ser usados com Defaults .
O tipo deve ter uma bridge variável estática que deve fazer referência a uma instância de um tipo que esteja em conformidade com Defaults.Bridge .
Defaults.Bridge public protocol DefaultsBridge {
associatedtype Value
associatedtype Serializable
func serialize ( _ value : Value ? ) -> Serializable ?
func deserialize ( _ object : Serializable ? ) -> Value ?
} Tipo: protocol
Uma Bridge é responsável pela serialização e desserialização.
Possui dois tipos associados Value e Serializable .
Value : o tipo que você deseja usar.Serializable : o tipo armazenado no UserDefaults .serialize : executado antes de armazenar os UserDefaults .deserialize : executado após a recuperação de seu valor dos UserDefaults . Defaults.AnySerializable Defaults . AnySerializable < Value : Defaults . Serializable > ( _ value : Value ) Tipo: class
Wrapper de tipo-tipo para Defaults.Serializable valores.
get<Value: Defaults.Serializable>() -> Value? : Recupere o valor qual tipo é Value do userDefaults.get<Value: Defaults.Serializable>(_: Value.Type) -> Value? : Especifique o Value que você deseja recuperar. Isso pode ser útil em alguns casos ambíguos.set<Value: Defaults.Serializable>(_ newValue: Value) : Defina um novo valor para Defaults.AnySerializable . Defaults.reset(keys…) Tipo: func
Redefina as teclas fornecidas de volta aos seus valores padrão.
Você também pode especificar teclas de string, que podem ser úteis se precisar armazenar algumas teclas em uma coleção, pois não é possível armazenar Defaults.Key .
Defaults.removeAll Defaults . removeAll ( suite : UserDefaults = . standard ) Tipo: func
Remova todas as entradas do conjunto de usuários do UserDefaults .
Defaults.withoutPropagation(_ closure:)Execute o fechamento sem desencadear eventos de mudança.
Quaisquer alterações de chave Defaults feitas dentro do fechamento não se propagam para os ouvintes de eventos Defaults ( Defaults.observe() e Defaults.publisher() ). Isso pode ser útil para evitar uma recursão infinita quando você deseja alterar uma chave no retorno de chamada ouvindo alterações para a mesma chave.
@Default(_ key:) Obtenha/defina um item Defaults e também faça com que a visualização SwiftUi seja atualizada quando o valor mudar.
Defaults.CollectionSerializable public protocol DefaultsCollectionSerializable : Collection , Defaults . Serializable {
init ( _ elements : [ Element ] )
} Tipo: protocol
Uma Collection que pode armazenar nos UserDefaults nativo.
Ele deve ter um init(_ elements: [Element]) para permitir que Defaults façam a desperalização.
Defaults.SetAlgebraSerializable public protocol DefaultsSetAlgebraSerializable : SetAlgebra , Defaults . Serializable {
func toArray ( ) -> [ Element ]
} Tipo: protocol
Uma SetAlgebra que pode armazenar nos nativos UserDefaults .
Deve ter uma função func toArray() -> [Element] para permitir que Defaults façam a serialização.
Embora Defaults já tenham suporte interno para muitos tipos, talvez seja necessário usar seu próprio tipo personalizado. O guia abaixo mostrará como fazer com que seu próprio tipo personalizado funcione com Defaults .
struct User {
let name : String
let age : String
}Defaults.Bridge , responsável pelo manuseio de serialização e desserialização. 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 esteja em conformidade com Defaults.Serializable . Sua ponte estática deve ser a ponte que criamos acima. 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" Pode haver situações em que você deseja usar [String: Any] diretamente, mas Defaults precisam de seus valores para se conformar para Defaults.Serializable . O Type-Eraser Defaults.AnySerializable ajuda a superar essa limitação.
Defaults.AnySerializable está disponível apenas para valores que estão em conformidade com o Defaults.Serializable .
Aviso: o Eraser do Tipo deve ser usado apenas quando não há outra maneira de lidar com isso, porque tem um desempenho muito pior. Ele deve ser usado apenas em tipos embrulhados. Por exemplo, embrulhado em Array , Set ou Dictionary .
Defaults.AnySerializable está em conformidade com ExpressibleByStringLiteral , ExpressibleByIntegerLiteral , ExpressibleByFloatLiteral , ExpressibleByBooleanLiteral , ExpressibleByNilLiteral , ExpressibleByArrayLiteral e ExpressibleByDictionaryLiteral .
O que significa que você pode atribuir esses tipos primitivos diretamente:
let any = Defaults . Key < Defaults . AnySerializable > ( " anyKey " , default : 1 )
Defaults [ any ] = " ? " get and setPara outros tipos, você terá que atribuir assim:
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 também suportam os tipos acima embrulhados em Array , Set , Dictionary .
Aqui está o exemplo para [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 mais exemplos, consulte testes/defaultsanySerializableTests.
Codable ambíguo Você pode ter um tipo que está em conformidade com Codable & NSSecureCoding ou uma enum Codable & RawRepresentable . Por padrão, Defaults preferem a conformidade Codable e usarão o CodableBridge para serializar -o em uma string json. Se você deseja serializar -o como um dado de codificação NSSecureCoding ou usar o valor bruto da enumeração RawRepresentable , você pode se conformar para Defaults.PreferNSSecureCoding ou Defaults.PreferRawRepresentable para substituir a ponte default:
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 não tivéssemos adicionado Defaults.PreferRawRepresentable "application/json"
Isso também pode ser útil se você conformar um tipo que você não controla para Defaults.Serializable Codable Ao definir explicitamente qual ponte usar, você garante que a representação armazenada sempre permaneça a mesma.
Collection personalizadoCollection e faça seus elementos em conformidade com 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 esteja em conformidade com 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!"SetAlgebraSetAlgebra e faça seus elementos em conformidade com 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 está em conformidade com 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 Após Defaults V5, você não precisa usar Codable para armazenar o dicionário, Defaults suportam o armazenamento de dicionários nativamente. Para obter tipos de suporte Defaults , consulte Tipos de suporte.
SwiftyUserDefaults ?É inspirado nesse pacote e em outras soluções. A principal diferença é que este módulo não code os valores padrão e vem com suporte codável.
Antigo
Você não pode usar Color.accentColor . ↩ ↩ 2