Swifty и Modern UserDefaults
Храните пары ключевых значений постоянно в течение запуска вашего приложения.
Он использует UserDefaults внизу, но обнажает защитный фасад с большим количеством хороших удобств.
Он используется в производстве всех моих приложений (4 миллиона+ пользователей).
UserDefaults .@AppStorageCodable .Toggle . Добавьте https://github.com/sindresorhus/Defaults на вкладке «Swift Package Manager» в Xcode.
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 пример или расширенное использование. Для получения дополнительных примеров см. Tests/DefaultStests.
Вы можете легко добавить поддержку для любого пользовательского типа.
Если тип соответствует как 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 :
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 .
Вы не можете использовать @Default в ObservableObject . Это предназначено для использования в View .
@ObservableDefault в @Observable С макросом @ObservableDefault вы можете использовать Defaults внутри классов @Observable , которые используют структуру наблюдения. Это так же просто, как импорт DefaultsMacros и добавление двух строк в свойство (обратите внимание, что добавление @ObservationIgnored необходимо для предотвращения столкновений с @Observable ):
Важный
Время сборки увеличится при использовании макросов.
Swift Macros зависят от пакета swift-syntax . Это означает, что когда вы компилируете код, который включает макросы в качестве зависимостей, вы также должны компилировать swift-syntax . Широко известно, что это оказывает серьезное влияние во время сборки, и, хотя это проблема, которая отслеживается (см. swift-syntax #2421), в настоящее время не реализовано решение.
import Defaults
import DefaultsMacros
@ Observable
final class UnicornManager {
@ ObservableDefault ( . hasUnicorn )
@ ObservationIgnored
var hasUnicorn : Bool
} Toggle Есть также обертка SwiftUI.Toggle , которая облегчает создание переключения на основе ключа Defaults со значением 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 )
}
}Вы также можете слушать изменения:
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 , он автоматически регистрирует значение default с обычными UserDefaults . Это означает, что вы можете использовать значение по умолчанию, например, привязки в интерфейсном застройщике.
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 (псевдоним 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 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? : Получить значение, какой тип является 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 набора.
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
Collection , которая может храниться в собственных UserDefaults .
У него должен быть инициализатор init(_ elements: [Element]) чтобы позволить Defaults сделать де-сериализацию.
Defaults.SetAlgebraSerializable public protocol DefaultsSetAlgebraSerializable : SetAlgebra , Defaults . Serializable {
func toArray ( ) -> [ Element ]
} Тип: protocol
SetAlgebra , которая может храниться в собственных UserDefaults .
Он должен иметь функцию func toArray() -> [Element] чтобы позволить Defaults выполнять сериализацию.
Хотя Defaults уже есть встроенная поддержка многих типов, вам, возможно, придется иметь возможность использовать свой собственный тип. Руководство ниже покажет вам, как сделать свой собственный тип работы с Defaults .
struct User {
let name : String
let age : String
}Defaults.Bridge 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
)
}
}User , который соответствует Defaults.Serializable . Его статический мост должен быть мостом, который мы создали выше. 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 . Тип-иразер Defaults.AnySerializable помогает преодолеть это ограничение.
Defaults.AnySerializable доступен только для значений, которые соответствуют Defaults.Serializable .
ПРЕДУПРЕЖДЕНИЕ. Его следует использовать только в оберщенных типах. Например, завернут в 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"
}Для получения дополнительных примеров см. Tests/DefaultAnySerializableTests.
Codable типа У вас может быть тип, который соответствует Codable & NSSecureCoding или Codable & RawRepresentable перечислению. По умолчанию по Defaults предпочитают Codable соответствие и использовать CodableBridge , чтобы сериализовать его на строку JSON. Если вы хотите сериализовать его как данные NSSecureCoding или использовать необработанное значение RawRepresentable перечисления, вы можете соответствовать 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 Если бы мы не добавили Defaults.PreferRawRepresentable PreferRawRepresentable, хранимым представлением было бы "application/json" вместо application/json .
Это также может быть полезно, если вы подчиняете тип Codable который вы не контролируете по Defaults.Serializable Явно определив, какой мост использовать, вы гарантируете, что хранимое представление всегда останется прежним.
CollectionCollection и сделайте его элементы соответствовать 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 , которая соответствует по 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 и сделайте его элементы соответствовать 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 , которое соответствует 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 После Defaults v5 вам не нужно использовать Codable для хранения словаря, Defaults поддерживают в сохранении словаря. По Defaults типов поддержки см. Типы поддержки.
SwiftyUserDefaults ?Это вдохновлено этим пакетом и другими решениями. Основное отличие состоит в том, что этот модуль не жестко кодирует значения по умолчанию и поставляется с кодируемой поддержкой.
Бывший
Вы не можете использовать Color.accentColor . ↩ ↩ 2