Movatterモバイル変換


[0]ホーム

URL:


3 min read

How to make swifty UserDefaults

Khoa Pham
Written by
How to make swifty UserDefaults

Issue#972

We want to have a swifty UserDefaults API that works with subscript and in a type safe manner.

extensionDefaults.Keys{staticletstring=Defaults.Key("string",default:"0")}XCTAssertEqual(defaults[.string],"0")defaults[.string]="1"XCTAssertEqual(defaults[.string],"1")

UserDefaults plist compatibility

DefineCompatible protocol that allows value to be plist compatible

The value parameter can be only property list objects: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. For NSArray and NSDictionary objects, their contents must be property list objects.

publicprotocolCompatible:Equatable{}extensionInt:Compatible{}extensionString:Compatible{}extensionBool:Compatible{}extensionDate:Compatible{}extensionArray:CompatiblewhereElement:Compatible{}extensionDictionary:CompatiblewhereKey:Compatible,Value:Compatible{}

Next, defineDefaults that acceptsUserDefaults as initialize dependency, so that we can swap UserDefaults.

Key with generic type

We defineKey with phantom typeValue so we know which value this key is pointing to, this makes it easier to reason about the code.

Since Swift has limitationStatic stored properties not supported in generic types, we can’t extend ourKey with static stored properties, we have to do via computed property

extensionDefaults.Key{staticvarstring:Defaults.Key<String>{.init("string",default:"0")}}

This works, but does not look nice. To workaround this, we define classAnyKey and make ourKey class as well and inherited thisAnyKey class.

Make a typealiastypealias Keys = AnyKey so we can refer toDefaults.Keys when we define our keys.

publicclassDefaults{publicvarsuite:UserDefaultspublicinit(suite:UserDefaults=.standard){self.suite=suite}publicsubscript<Value:Compatible>(key:Key<Value>)->Value{get{ifletvalue=suite.object(forKey:key.name)as?Value{returnvalue}returnkey.defaultValue}set{suite.set(newValue,forKey:key.name)}}publicfuncexists<Value:Compatible>(key:Key<Value>)->Bool{suite.object(forKey:key.name)!=nil}}extensionDefaults{publictypealiasKeys=AnyKeypublicclassKey<Value:Compatible>:AnyKey{vardefaultValue:Valuepublicinit(_name:String,defaultdefaultValue:Value){self.defaultValue=defaultValuesuper.init(name:name)}}publicclassAnyKey{varname:Stringinit(name:String){self.name=name}}}extensionDefaults.AnyKey:Equatable{publicstaticfunc==(lhs:Defaults.AnyKey,rhs:Defaults.AnyKey)->Bool{lhs.name==rhs.name}}extensionDefaults.AnyKey:Hashable{publicfunchash(intohasher:inoutHasher){hasher.combine(name)}}

How about Optional

We can supportOptional as well, as long as it’s underlying value is compatible. Since the type is defined viaKey, we can’t accidentally use Optional when the Key has non Optional value

extensionOptional:CompatiblewhereWrapped:Compatible{}extensionDefaults{publicsubscript<Value:Compatible>(key:Key<Optional<Value>>)->Value?{get{ifletvalue=suite.object(forKey:key.name)as?Value{returnvalue}returnnil}set{ifletnewValue{suite.set(newValue,forKey:key.name)}else{suite.removeObject(forKey:key.name)}}}}extensionDefaults.Keys{staticletoptional=Defaults.Key<Int?>("optional.int",default:nil)}functestOptional(){XCTAssertNil(defaults[.optional])defaults[.optional]=1XCTAssertEqual(defaults[.optional],1)defaults[.optional]=nilXCTAssertNil(defaults[.optional])}
Khoa Pham
Written by

I’m open source contributor, writer, speaker and product maker.

Start the conversation

How to use playground in Swift file

Issue#998

Traditionally, if we wanted to quickly test a function or a piece of logic, we would open a separate Playground file (.playground).

From Xcode 26 we can have access to the new#Playground macro in Swift 6.2. This allows us to declare a …

Khoa Pham
Written by

How to morph liquid glass view transition

Issue#997

An interesting feature in iOS 26 is the ability to create morph “Liquid Glass” effects, where views with the.glassEffect() modifier can fluidly morph into one another. This is achieved usingGlassEffectContainer and the …

Khoa Pham
Written by

How to prompt users to configure widgets in iOS 18

Issue#996

When creating widgets for iOS, especially those that need user input to be useful, one common challenge is guiding the user to configure them. Before iOS 18, a user would add a widget, but then would have to know to long-press it and …

Khoa Pham
Written by

How to make zoom transition animation in iOS 18

Issue#995

With iOS 18, SwiftUI introducesmatchedTransitionSource andnavigationtransition as a powerful new way to create zoom animations between views. This allows you to smoothly transition from a small view to a larger, more detailed view, …

Khoa Pham
Written by

[8]ページ先頭

©2009-2025 Movatter.jp