SwiftyとModern UserDefaults
アプリの起動全体にキー価値のペアを永続的に保存します。
その下にUserDefaultsを使用しますが、多くの素敵な便利さでタイプセーフファサードを公開します。
私のすべてのアプリ(400万人以上のユーザー)が生産に使用しています。
UserDefaults値が変更されたときにビューを更新するプロパティラッパー。@AppStorageよりも利益Codable多くのタイプをサポートします。Toggleコンポーネントが付属しています。 Xcodeの「Swift Package Manager」タブにhttps://github.com/sindresorhus/Defaults追加します。
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例、または高度な使用法を参照してください。その他の例については、テスト/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あることに注意してください。
ObservableObjectで@Defaultを使用することはできません。 Viewで使用することを意図しています。
@ObservableDefault in @Observable @ObservableDefault Macroを使用すると、観測フレームワークを使用する@Observableクラス内のDefaultsを使用できます。そうすることは、 DefaultsMacrosをインポートし、プロパティに2行を追加するのと同じくらい簡単です( @ObservationIgnored追加することは、 @Observableとの衝突を防ぐために必要であることに注意してください):
重要
マクロを使用すると、ビルド時間が増加します。
Swiftマクロはswift-syntaxパッケージに依存します。これは、マクロを依存関係として含めるコードをコンパイルする場合、 swift-syntaxをコンパイルする必要があることを意味します。そうすることはビルド時間に深刻な影響を与えることは広く知られており、それは追跡されている問題ですが( swift-syntax #2421を参照)、現在実装されているソリューションはありません。
import Defaults
import DefaultsMacros
@ Observable
final class UnicornManager {
@ ObservableDefault ( . hasUnicorn )
@ ObservationIgnored
var hasUnicorn : Bool
} Toggleまた、 SwiftUI.Toggleラッパーもあり、 Bool値を持つDefaultsキーに基づいてトグルを簡単に作成できます。
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を作成すると、通常のUserDefaultsでdefault値を自動的に登録します。これは、たとえば、インターフェイスビルダーのバインディングなど、デフォルト値を使用できることを意味します。
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で使用できます。
このタイプには、 Defaults.Bridgeに適合するタイプのインスタンスを参照する静的変数bridgeが必要です。
Defaults.Bridge public protocol DefaultsBridge {
associatedtype Value
associatedtype Serializable
func serialize ( _ value : Value ? ) -> Serializable ?
func deserialize ( _ object : Serializable ? ) -> Value ?
}タイプ: protocol
Bridgeは、シリアル化と脱介入を担当します。
2つの関連タイプ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? :userdefaultsから値が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
ネイティブUserDefaultsに保存できるCollection 。
Defaults de-Serializationを実行させるには、初期化init(_ elements: [Element])が必要です。
Defaults.SetAlgebraSerializable public protocol DefaultsSetAlgebraSerializable : SetAlgebra , Defaults . Serializable {
func toArray ( ) -> [ Element ]
}タイプ: protocol
ネイティブのUserDefaultsに保存できるSetAlgebra 。
Defaultsシリアル化を実行できるようにするには、関数func toArray() -> [Element]が必要です。
Defaultsはすでに多くのタイプのサポートが組み込まれていますが、独自のカスタムタイプを使用できる必要がある場合があります。以下のガイドでは、 Defaultsで独自のカスタムタイプを機能させる方法を示します。
struct User {
let name : String
let age : String
}Defaults.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
)
}
}Defaults.Serializableに準拠するUserの拡張機能を作成します。その静的ブリッジは、上記で作成した橋でなければなりません。 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に準拠するためにその値が必要です。Serializable。タイプエラサーDefaults.AnySerializable 、この制限を克服するのに役立ちます。
Defaults.AnySerializable 、 Defaults.Serializableに適合する値に対してのみ使用できます。
警告:タイプエラサーは、パフォーマンスがはるかに悪いため、他の方法がない場合にのみ使用する必要があります。ラップされたタイプでのみ使用する必要があります。たとえば、 Array 、 Set 、またはDictionaryで包まれています。
Defaults.AnySerializable ExpressibleByArrayLiteral ExpressibleByStringLiteral 、 ExpressibleByIntegerLiteral 、 ExpressibleByFloatLiteral 、 ExpressibleByBooleanLiteral 、 ExpressibleByNilLiteral 、およびExpressibleByDictionaryLiteralに準拠しています。
つまり、これらのプリミティブタイプを直接割り当てることができます。
let any = Defaults . Key < Defaults . AnySerializable > ( " anyKey " , default : 1 )
Defaults [ any ] = " ? " get and 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"
}その他の例については、テスト/defaultsanyserializabletestsを参照してください。
Codableタイプのシリアル化Codable & NSSecureCodingまたはCodable & RawRepresentable Enumに適合するタイプがある場合があります。デフォルトでは、 Defaults Codable Compormanceを好み、 CodableBridgeを使用してJSON文字列にシリアル化します。 NSSecureCodingデータとしてシリアル化する場合、またはRawRepresentable ENUMの生値を使用する場合は、 Defaults.PreferNSSecureCodingに適合できます。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追加しなければ、保存された表現は、 application/json "application/json"だったでしょう。
これは、 Defaults.Serializableに制御しないタイプを適合させる場合にも役立ちます。タイプはいつでもCodable適合性を受け取ることができ、保存された表現が変更され、値を読み取れないものにする可能性があります。どのブリッジを使用するかを明示的に定義することにより、保存された表現が常に同じままであることを確認します。
CollectionタイプCollectionを作成し、その要素を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 ]
}
}Defaults.CollectionSerializableに適合するBagの拡張機能を作成します。 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タイプSetAlgebraを作成し、その要素を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 )
}
}Defaults.SetAlgebraSerializableに適合するSetBagの拡張機能を作成します 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