Swift Dictionary default value
Using Swift 2 you can achieve something similar to python's version with an extension of Dictionary
:
// Values which can provide a default instance
protocol Initializable {
init()
}
extension Dictionary where Value: Initializable {
// using key as external name to make it unambiguous from the standard subscript
subscript(key key: Key) -> Value {
mutating get { return self[key, or: Value()] }
set { self[key] = newValue }
}
}
// this can also be used in Swift 1.x
extension Dictionary {
subscript(key: Key, or def: Value) -> Value {
mutating get {
return self[key] ?? {
// assign default value if self[key] is nil
self[key] = def
return def
}()
}
set { self[key] = newValue }
}
}
The closure after the ??
is used for classes since they don't propagate their value mutation (only "pointer mutation"; reference types).
The dictionaries have to be mutable (var
) in order to use those subscripts:
// Make Int Initializable. Int() == 0
extension Int: Initializable {}
var dict = [Int: Int]()
dict[1, or: 0]++
dict[key: 2]++
// if Value is not Initializable
var dict = [Int: Double]()
dict[1, or: 0.0]
Idiomatic way to construct a default value for a dictionary key in swift and set it
There is no way to set a default value for a Dictionary
, but you can specify a default
value when looking up a dictionary value:
var arr = myDict["key", default: []]
default
provides a default value if the key is not found. In this case, you can use []
which Swift will infer to be an empty array of type [String]
.
Using an enum's default value as a dictionary key without explicitly casting to String
Update and improvement for Swift 4.2
extension Dictionary {
subscript(key: APIKeys) -> Value? {
get {
guard let key = key.stringValue as? Key else { return nil }
return self[key]
}
set(value) {
guard let key = key.stringValue as? Key else { return }
guard let value = value else { self.removeValue(forKey: key); return }
self.updateValue(value, forKey: key)
}
}
}
protocol APIKeys {}
extension APIKeys {
var stringValue: String {
return String(describing: self)
}
}
enum Keys: APIKeys {
case key_one
case key_two
}
var myStringDict = [AnyHashable : Any]()
var model1StringDict = [String : Any]()
var model2StringDict = [String : String]()
myStringDict.updateValue("firstValue", forKey: Keys.key_one.stringValue) // [key_one: firstValue]
myStringDict[Keys.key_two] = "secondValue" // [key_two: secondValue, key_one: firstValue]
myStringDict[Keys.key_one] = nil // [key_two: secondValue]
myStringDict.removeValue(forKey: Keys.key_two.stringValue) // []
model1StringDict.updateValue("firstValue", forKey: Model1Keys.model_1_key_one.stringValue) // [model_1_key_one: firstValue]
model1StringDict[Model1Keys.model_1_key_two] = "secondValue" // [model_1_key_two: secondValue, model_1_key_one: firstValue]
model1StringDict[Model1Keys.model_1_key_one] = nil // [model_1_key_two: secondValue]
model2StringDict.updateValue("firstValue", forKey: Model2Keys.model_2_key_one.stringValue) // [model_2_key_one: firstValue]
model2StringDict[Model2Keys.model_2_key_two] = "secondValue" // [model_2_key_two: secondValue, model_2_key_one: firstValue]
model2StringDict[Model2Keys.model_2_key_one] = nil // [model_2_key_two: secondValue]
I specifically changed the types of the 3 dictionaries to show the common ways of typing a dictionary ([AnyHashable : Any]
, [String : Any]
, and [String : String]
), and showed that this works with each of the types.
It's important to note that if you use updateValue
instead of the assignment operator when the key of your dictionary is AnyHashable
, then you need to specify the String
value of the key with .stringValue
. Otherwise, the exact type of the key being stored will not explicitly be a String
, and it'll get messed up later if you try to, say, remove a value under your key via assigning nil
to that key. For a dictionary where the key is specifically typed to be String
, then the updateValue
function will have a compile time error saying that the key needs to be a String
, so you can't mess it up that way.
Swift 2.3
I figured out the extension solution that I wanted.
extension Dictionary {
subscript(key: Keys) -> Value? {
get {
return self[String(key) as! Key]
}
set(value) {
guard
let value = value else {
self.removeValueForKey(String(key) as! Key)
return
}
self.updateValue(value, forKey: String(key) as! Key)
}
}
}
enum Keys {
case key_one
case key_two
}
var myStringDict = [String : String]()
/// Adding the first key value through the String() way on purpose
myStringDict.updateValue("firstValue", forKey: String(Keys.key_one))
// myStringDict: ["key_one": "firstValue"]
// myStringDict[Keys.key_one]!: firstValue
myStringDict[Keys.key_two] = "secondValue"
// myStringDict: ["key_one": "firstValue", "key_two": "secondValue"]
myStringDict[Keys.key_one] = nil
// myStringDict: ["key_two": "secondValue"]
Notice that the declared dictionary key type is String
, but I'm able to just use Keys.key_one
and the subscript in the dictionary extension takes care of the rest.
I can probably put some better guarding around the as!
conversion to Key
, but I'm not sure it's needed, as I know that my enum can always be converted to a valid Key
by the String()
cast.
Improvement to answer
Even better, since I'm using this for API Keys, I made a blank protocol called APIKeys
and each model will implement their own Keys
enum that conforms to the APIKeys
protocol. And the dictionary's subscript is updated to take in APIKeys
as the Key
value.
extension Dictionary {
subscript(key: APIKeys) -> Value? {
get {
return self[String(key) as! Key]
}
set(value) {
guard
let value = value else {
self.removeValueForKey(String(key) as! Key)
return
}
self.updateValue(value, forKey: String(key) as! Key)
}
}
}
protocol APIKeys {}
enum Keys: APIKeys {
case key_one
case key_two
}
enum Model1Keys: APIKeys {
case model_1_key_one
case model_1_key_two
}
enum Model2Keys: APIKeys {
case model_2_key_one
case model_2_key_two
}
var myStringDict = [String : String]()
var model1StringDict = [String : String]()
var model2StringDict = [String : String]()
myStringDict.updateValue("firstValue", forKey: String(Keys.key_one)) // myStringDict: ["key_one": "firstValue"]
myStringDict[Keys.key_two] = "secondValue" // myStringDict: ["key_one": "firstValue", "key_two": "secondValue"]
myStringDict[Keys.key_one] = nil // myStringDict: ["key_two": "secondValue"]
model1StringDict.updateValue("firstValue", forKey: String(Model1Keys.model_1_key_one)) // model1StringDict: ["model_1_key_one": "firstValue"]
model1StringDict[Model1Keys.model_1_key_two] = "secondValue" // model1StringDict: ["model_1_key_one": "firstValue", "model_1_key_two": "secondValue"]
model1StringDict[Model1Keys.model_1_key_one] = nil // model1StringDict: ["model_1_key_two": "secondValue"]
model2StringDict.updateValue("firstValue", forKey: String(Model2Keys.model_2_key_one)) // model2StringDict: ["model_2_key_one": "firstValue"]
model2StringDict[Model2Keys.model_2_key_two] = "secondValue" // model2StringDict: ["model_2_key_one": "firstValue", "model_2_key_two": "secondValue"]
model2StringDict[Model2Keys.model_2_key_one] = nil // model2StringDict: ["model_2_key_two": "secondValue"]
Most optimal way to increment or initialize an Int in a Swift Dictionary
Update
Since Swift 4 you can use a default value in the subscript (assuming the dictionary has Value == Int
):
dict[key, default: 0] += 1
You can read more about it in the Swift Evolution proposal Dictionary & Set Enhancements under Key-based subscript with default value
One way would be to use the nil coalescing operator:
dict[key] = ((dict[key] as? Int) ?? 0) + 1
If you know the type of the dictionary you can almost do the same but without casting:
dict[key] = (dict[key] ?? 0) + 1
Explanation:
This operator (??
) takes an optional on the left side and a non optional on the right side and returns the value of the optional if it is not nil
. Otherwise it returns the right value as default one.
Like this ternary operator expression:
dict[key] ?? 0
// is equivalent to
dict[key] != nil ? dict[key]! : 0
How to set the default value of a Date in Swift?
You can set current date of device with giving Date()
which gives you a current date of device
Does the Swift standard Dictionary have a get-or-set function?
No it doesn't have a function like this. It is fine to create an extension in this circumstance.
Swift 3 equivalent of setting a value to a dictionary key if the key doesn't exist
The Nil-Coalescing Operator
foo["someArg"] = foo["someArg"] ?? "default value"
The nil-coalescing operator (
a ?? b
) unwraps an optionala
if it contains a value, or returns a default valueb
if a isnil
.
If copy-on-write performance is a concern, just use an if
statement:
if foo["someArg"] == nil { foo["someArg"] = "default value" }
Swift 4 Key Value Dictionary stored in User Defaults
There is a major mistake. You save a dictionary but retrieve an array.
Apart from that a dictionary retrieved from UserDefaults
is [String:Any]
by default, you have to conditional downcast the object.
The code checks if there is a dictionary in UserDefaults
and if there is the requested key in one expression
public func checkDatabaseMatch( _ name: String, _ number: String) -> Bool
{
guard let database = UserDefaults.standard.dictionary(forKey: "Database") as? [String:String],
let databaseCheck = database[name] else { return false }
return databaseCheck == number
}
Another mistake is that you are always overwriting the entire dictionary in UserDefaults
. If you want to save multiple key-value pairs you have to read the dictionary first.
public func saveToDatabase( _ name: String, _ number: String)
{
var newEntry : [String: String]
if let database = UserDefaults.standard.dictionary(forKey: "Database") as? [String:String] {
newEntry = database
} else {
newEntry = [:]
}
newEntry[name] = number
UserDefaults.standard.set(newEntry, forKey: "Database")
}
Side note: The parameter labels are highly recommended in Swift for better readability.
Related Topics
What Causes 'Constant Captured by a Closure Before Being Initialized' Error
Swift - Could Not Cast Value of Type '_Nscfstring' to 'Nsdictionary'
Trouble Using Callbacks with Cgpattern in Swift3
Read-Only Properties of Protocols in Swift
Swiftui: Global Overlay That Can Be Triggered from Any View
How to Go Back to the Initial View Controller in Swift
Round Up Double to 2 Decimal Places
Is There a Method to Blur a Background in Swiftui
Arkit - Get Current Position of Arcamera in a Scene
Highlight a Specific Part of the Text in Swiftui
How May I Test the Equivalency of Enumeration Cases with Associated Values in Swift 4
Transparent Background for Texteditor in Swiftui
Trying to Know When a Window Closes in a MACos Document Based Application
Custom Segue Transition Animation
Firebase Query Ordering Not Working Properly