UserDefaults yang Swifty dan Modern
Simpan pasangan nilai kunci secara terus-menerus melintasi peluncuran aplikasi Anda.
Menggunakan UserDefaults di bawahnya tetapi memperlihatkan fasad yang aman jenis dengan banyak kenyamanan yang bagus.
Ini digunakan dalam produksi oleh semua aplikasi saya (4 juta+ pengguna).
UserDefaults berubah.@AppStorageCodable .Toggle SwiftUi. Tambahkan https://github.com/sindresorhus/Defaults di tab "Swift Package Manager" di Xcode.
Int(8/16/32/64)UInt(8/16/32/64)DoubleCGFloatFloatStringBoolDateDataURLUUIDRangeClosedRangeCodableNSSecureCodingColor 1 (SwiftUi)Color.Resolved 1 (SwiftUi)NSColorUIColorNSFontDescriptorUIFontDescriptor Default juga mendukung jenis di atas yang dibungkus dengan Array , Set , Dictionary , Range , ClosedRange , dan bahkan dibungkus dengan tipe bersarang. Misalnya, [[String: Set<[String: Int]>]] .
Untuk lebih banyak jenis, lihat contoh enum, contoh Codable , atau penggunaan lanjutan. Untuk lebih banyak contoh, lihat Tes/DefaultStests.
Anda dapat dengan mudah menambahkan dukungan untuk jenis khusus apa pun.
Jika tipe sesuai dengan NSSecureCoding dan Codable , maka Codable akan digunakan untuk serialisasi.
Dokumentasi API.
Anda mendeklarasikan kunci default di muka dengan jenis dan nilai default.
Nama kunci harus ASCII, bukan mulai dengan @ , dan tidak dapat berisi titik ( . ).
import Defaults
extension Defaults . Keys {
static let quality = Key < Double > ( " quality " , default : 0.8 )
// ^ ^ ^ ^
// Key Type UserDefaults name Default value
} Anda kemudian dapat mengaksesnya sebagai subskrip di global 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']Anda juga dapat mendeklarasikan kunci opsional ketika Anda tidak ingin menyatakan nilai default di muka:
extension Defaults . Keys {
static let name = Key < Double ? > ( " name " )
}
if let name = Defaults [ . name ] {
print ( name )
} Nilai default kemudian nil .
Anda juga dapat menentukan nilai default dinamis. Ini bisa berguna ketika nilai default dapat berubah selama masa pakai aplikasi:
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"(Ini berfungsi selama nilai mentah enum adalah tipe yang didukung)
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" Anda tidak diharuskan untuk melampirkan kunci ke Defaults.Keys .
let isUnicorn = Defaults . Key < Bool > ( " isUnicorn " , default : true )
Defaults [ isUnicorn ]
//=> true@Default di View Anda dapat menggunakan pembungkus properti @Default untuk mendapatkan/mengatur item Defaults dan juga memiliki tampilan diperbarui saat nilainya berubah. Ini mirip dengan @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 ( )
}
}
} Perhatikan bahwa itu @Default , bukan @Defaults .
Anda tidak dapat menggunakan @Default di objek ObservableObject . Itu dimaksudkan untuk digunakan dalam sebuah View .
@ObservableDefault di @Observable Dengan makro @ObservableDefault , Anda dapat menggunakan Defaults di dalam kelas @Observable yang menggunakan kerangka pengamatan. Melakukan hal itu sesederhana mengimpor DefaultsMacros dan menambahkan dua baris ke properti (perhatikan bahwa menambahkan @ObservationIgnored diperlukan untuk mencegah bentrokan dengan @Observable ):
Penting
Waktu membangun akan meningkat saat menggunakan makro.
Makro Swift bergantung pada paket swift-syntax . Ini berarti bahwa ketika Anda mengkompilasi kode yang menyertakan makro sebagai dependensi, Anda juga harus mengkompilasi swift-syntax . Diketahui secara luas bahwa melakukan hal itu memiliki dampak serius dalam waktu pembangunan dan, meskipun merupakan masalah yang sedang dilacak (lihat swift-syntax #2421), saat ini tidak ada solusi yang diterapkan.
import Defaults
import DefaultsMacros
@ Observable
final class UnicornManager {
@ ObservableDefault ( . hasUnicorn )
@ ObservationIgnored
var hasUnicorn : Bool
} Toggle Ada juga pembungkus SwiftUI.Toggle yang membuatnya lebih mudah untuk membuat sakelar berdasarkan kunci Defaults dengan nilai 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 )
}
}Anda juga dapat mendengarkan perubahan:
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 )
}
} Berbeda dengan pengamatan utama UserDefaults asli, di sini Anda menerima objek perubahan yang sangat diketik.
extension Defaults . Keys {
static let isUnicornMode = Key < Bool > ( " isUnicornMode " , default : false )
}
Defaults [ . isUnicornMode ] = true
//=> true
Defaults . reset ( . isUnicornMode )
Defaults [ . isUnicornMode ]
//=> false Ini berfungsi untuk Key dengan opsional juga, yang akan diatur ulang kembali ke nil .
Perubahan yang dilakukan dalam Defaults.withoutPropagation Tanpa penutupanpropagasi tidak akan disebarkan ke callback pengamatan ( Defaults.observe() atau Defaults.publisher() ), dan karenanya dapat mencegah rekursi yang tak terbatas.
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 dengan SugarIni berfungsi juga:
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 Saat Anda membuat Defaults.Key , secara otomatis mendaftarkan nilai default dengan UserDefaults normal. Ini berarti Anda dapat menggunakan nilai default dalam, misalnya, binding di Interface Builder.
extension Defaults . Keys {
static let isUnicornMode = Key < Bool > ( " isUnicornMode " , default : true )
}
print ( UserDefaults . standard . bool ( forKey : Defaults . Keys . isUnicornMode . name ) )
//=> trueCatatan
Defaults.Keydengan nilai default dinamis tidak akan mendaftarkan nilai default diUserDefaults.
DefaultsDefaults.Keys Jenis: class
Menyimpan kunci.
Defaults.Key (alias Defaults.Keys.Key ) Defaults . Key < T > ( _ name : String , default : T , suite : UserDefaults = . standard ) Jenis: class
Buat tombol dengan nilai default.
Nilai default ditulis ke UserDefaults yang sebenarnya dan dapat digunakan di tempat lain. Misalnya, dengan pengikatan pembangun antarmuka.
Defaults.Serializable public protocol DefaultsSerializable {
typealias Value = Bridge . Value
typealias Serializable = Bridge . Serializable
associatedtype Bridge : Defaults . Bridge
static var bridge : Bridge { get }
} Jenis: protocol
Jenis yang sesuai dengan protokol ini dapat digunakan dengan Defaults .
Jenis harus memiliki bridge variabel statis yang seharusnya merujuk pada suatu contoh jenis yang sesuai dengan Defaults.Bridge .
Defaults.Bridge public protocol DefaultsBridge {
associatedtype Value
associatedtype Serializable
func serialize ( _ value : Value ? ) -> Serializable ?
func deserialize ( _ object : Serializable ? ) -> Value ?
} Jenis: protocol
Bridge bertanggung jawab untuk serialisasi dan deserialisasi.
Ini memiliki dua Value jenis terkait dan Serializable .
Value : Jenis yang ingin Anda gunakan.Serializable : Jenis yang disimpan di UserDefaults .serialize : Dieksekusi sebelum disimpan ke UserDefaults .deserialize : Dieksekusi setelah mengambil nilainya dari UserDefaults . Defaults.AnySerializable Defaults . AnySerializable < Value : Defaults . Serializable > ( _ value : Value ) Jenis: class
Type-Erased Wrapper untuk Defaults.Serializable Nilai.
get<Value: Defaults.Serializable>() -> Value? : Ambil nilai jenis mana yang merupakan Value dari UserDefaults.get<Value: Defaults.Serializable>(_: Value.Type) -> Value? : Tentukan Value yang ingin Anda ambil. Ini bisa berguna dalam beberapa kasus yang ambigu.set<Value: Defaults.Serializable>(_ newValue: Value) : Tetapkan nilai baru untuk Defaults.AnySerializable . Defaults.reset(keys…) Jenis: func
Setel ulang tombol yang diberikan kembali ke nilai default mereka.
Anda juga dapat menentukan tombol string, yang dapat berguna jika Anda perlu menyimpan beberapa kunci dalam koleksi, karena tidak mungkin untuk menyimpan Defaults.Key dalam koleksi karena generik.
Defaults.removeAll Defaults . removeAll ( suite : UserDefaults = . standard ) Jenis: func
Hapus semua entri dari suite UserDefaults yang diberikan.
Defaults.withoutPropagation(_ closure:)Jalankan penutupan tanpa memicu peristiwa perubahan.
Setiap perubahan kunci Defaults yang dibuat dalam penutupan tidak akan menyebar ke pendengar acara Defaults ( Defaults.observe() dan Defaults.publisher() ). Ini bisa berguna untuk mencegah rekursi tak terbatas ketika Anda ingin mengubah kunci dalam panggilan balik mendengarkan perubahan untuk kunci yang sama.
@Default(_ key:) Dapat/atur item Defaults dan juga mintalah tampilan SwiftUi diperbarui saat nilainya berubah.
Defaults.CollectionSerializable public protocol DefaultsCollectionSerializable : Collection , Defaults . Serializable {
init ( _ elements : [ Element ] )
} Jenis: protocol
Collection yang dapat disimpan ke dalam UserDefaults asli.
Seharusnya memiliki initializer init(_ elements: [Element]) untuk membiarkan Defaults melakukan de-serialisasi.
Defaults.SetAlgebraSerializable public protocol DefaultsSetAlgebraSerializable : SetAlgebra , Defaults . Serializable {
func toArray ( ) -> [ Element ]
} Jenis: protocol
SetAlgebra yang dapat disimpan ke dalam UserDefaults asli.
Seharusnya memiliki fungsi func toArray() -> [Element] untuk membiarkan Defaults melakukan serialisasi.
Meskipun Defaults sudah memiliki dukungan bawaan untuk banyak jenis, Anda mungkin harus dapat menggunakan jenis kustom Anda sendiri. Panduan di bawah ini akan menunjukkan kepada Anda cara membuat jenis kustom Anda sendiri berfungsi dengan Defaults .
struct User {
let name : String
let age : String
}Defaults.Bridge , yang bertanggung jawab untuk menangani serialisasi dan deserialisasi. 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 yang sesuai dengan Defaults.Serializable . Jembatan statisnya harus menjadi jembatan yang kami buat di atas. 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" Mungkin ada situasi di mana Anda ingin menggunakan [String: Any] secara langsung, tetapi Defaults membutuhkan nilainya untuk menyesuaikan diri dengan Defaults.Serializable . Tipe-Eraser Defaults.AnySerializable membantu mengatasi batasan ini.
Defaults.AnySerializable hanya tersedia untuk nilai yang sesuai dengan Defaults.Serializable .
PERINGATAN: Tipe-eraser hanya boleh digunakan ketika tidak ada cara lain untuk menanganinya karena memiliki kinerja yang jauh lebih buruk. Itu hanya boleh digunakan dalam jenis yang dibungkus. Misalnya, dibungkus dengan Array , Set atau Dictionary .
Defaults.AnySerializable sesuai dengan ekspresi yang dapat ExpressibleByStringLiteral , ExpressibleByIntegerLiteral , ke mana -mana yang dapat diekspresikan, ExpressibleByFloatLiteral , diekspresikan ExpressibleByBooleanLiteral , yang dapat diekspresikan, ExpressibleByNilLiteral , yang dapat diekspresikan, ExpressibleByArrayLiteral , dan ExpressibleByDictionaryLiteral .
Yang berarti Anda dapat menetapkan tipe primitif ini secara langsung:
let any = Defaults . Key < Defaults . AnySerializable > ( " anyKey " , default : 1 )
Defaults [ any ] = " ? " get and setUntuk jenis lain, Anda harus menetapkannya seperti ini:
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 , atau Dictionary Defaults.AnySerializable juga mendukung jenis di atas yang dibungkus dengan Array , Set , Dictionary .
Berikut adalah contoh untuk [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"
}Untuk contoh lebih lanjut, lihat tes/defaultsanyyserializableTests.
Codable ambigu Anda mungkin memiliki jenis yang sesuai dengan Codable & NSSecureCoding atau enum Codable & RawRepresentable . Secara default, Defaults akan lebih suka kesesuaian Codable dan menggunakan CodableBridge untuk membuat serialisasi menjadi string JSON. Jika Anda ingin membuat serial sebagai data NSSecureCoding atau Defaults.PreferRawRepresentable nilai mentah dari enum RawRepresentable , Anda dapat menyesuaikan diri dengan 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 Seandainya kami tidak menambahkan Defaults.PreferRawRepresentable , representasi yang disimpan akan menjadi "application/json" alih -alih application/json .
Ini juga dapat bermanfaat jika Anda menyesuaikan jenis yang tidak Anda kendalikan ke Defaults.Serializable karena jenis dapat menerima kesesuaian Codable kapan saja dan kemudian representasi yang disimpan akan berubah, yang dapat membuat nilai tidak dapat dibaca. Dengan secara eksplisit mendefinisikan jembatan mana yang akan digunakan, Anda memastikan representasi yang disimpan akan selalu tetap sama.
Collection KustomCollection Anda dan buat elemennya sesuai dengan 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 yang sesuai dengan 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 KustomSetAlgebra Anda dan buat elemennya sesuai dengan 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 yang sesuai dengan 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 Setelah Defaults V5, Anda tidak perlu menggunakan Codable untuk menyimpan kamus, dukungan Defaults menyimpan kamus secara asli. Untuk jenis dukungan Defaults , lihat jenis dukungan.
SwiftyUserDefaults ?Ini terinspirasi oleh paket itu dan solusi lainnya. Perbedaan utama adalah bahwa modul ini tidak menggunakan kode nilai default dan dilengkapi dengan dukungan yang dapat dikodekan.
Mantan
Anda tidak dapat menggunakan Color.accentColor . ↩ ↩ 2