What is the difference between initializing a dictionary and declaring in Swift? - swift

Not sure if my title is accurate. Please comment if so will update.
In one method I just create the dictionary and initialize it with ()
In the other I create it and immediately fill it with a key value pair.
What is the difference? Is one prefered over the other?
//initializing dictionary
var airPortCodesInitialize = [Int: String]()
//vs declaring
var airPortCodes: [String: String] = ["SLC": "Salt Lake City", "LAX": "Los Angeles"]

In both cases, you are declaring a dictionary and initializing it. The only difference is that the first line creates an empty dictionary, the second line creates a dictionary filled with key value pairs.
A single declaration looks like this:
var myDictionary: [String: String]
I think you misunderstood what declarations are, so for the rest of this answer, I will compare the line above and your first line.
What is the difference?
A single declaration gives the variable no value, so if you try to use myDictionary immediately after the declaration without initializing it, the compiler gives you an error:
print(myDictionary["Hello"]) // error
Is one prefered over the other?
Most of the time, you should put the initialisation and declaration on the same line like you did in
var airPortCodesInitialize = [Int: String]()
This is more readable.
Sometimes though, you might want different initial values for a constant dictionary depending on a value. Then you must separate the declaration and initialisation:
let myConstantDict: [String: String]
switch something {
case .foo:
myConstantDict = ...
case .bar:
myConstantDict = ...
}

Related

String as Member Name in Swift

I have an array of strings and a CoreData object with a bunch of variables stored in it; the strings represent each stored variable. I want to show the value of each of the variables in a list. However, I cannot find a way to fetch all variables from a coredata object, and so instead I'm trying to use the following code.
ListView: View{
//I call this view from another one and pass in the object.
let object: Object
//I have a bunch of strings for each variable, this is just a few of them
let strings = ["first_name", "_last_name", "middle_initial" ...]
var body: some View{
List{
ForEach(strings){ str in
//Want to pass in string here as property name
object.str
//This doesn't work because string cannot be directly passed in as property name - this is the essence of my question.
}
}
}
}
So as you can see, I just want to pass in the string name as a member name for the CoreData object. When I try the code above, I get the following errors: Value of type 'Object' has no member 'name' and Expected member name following '.'. Please tell me how to pass in the string as a property name.
CoreData is heavily based on KVC (Key-Value Coding) so you can use key paths which is much more reliable than string literals.
let paths : [KeyPath<Object,String>] = [\.first_name, \.last_name, \.middle_initial]
...
ForEach(paths, id: \.self){ path in
Text(object[keyPath: path]))
}
Swift is a strongly typed language, and iterating in a python/javascript like approach is less common and less recommended.
Having said that, to my best knowledge you have three ways to tackle this issue.
First, I'd suggest encoding the CoreData model into a dictionary [String: Any] or [String: String] - then you can keep the same approach you wanted - iterate over the property names array and get them as follow:
let dic = object.asDictionary()
ForEach(strings){ str in
//Want to pass in string here as property name
let propertyValue = dic[str]
//This doesn't work because string cannot be directly passed in as property name - this is the essence of my question.
}
Make sure to comply with Encodable and to have this extension
extension Encodable {
func asDictionary() throws -> [String: Any] {
let data = try JSONEncoder().encode(self)
guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {
throw NSError()
}
return dictionary
}
Second, you can hard coded the properties and if/else/switch over them in the loop
ForEach(strings){ str in
//Want to pass in string here as property name
switch str {
case "first_name":
// Do what is needed
}
}
Third, and last, You can read and use a technique called reflection, which is the closest thing to what you want to achieve
link1
link2

Swift: Dictionary of Dicitionaries, cannot get subscript

I've looked at other subscript issues here and I don't think they match my problem. I have a dictionary of dictionaries - Dictionary[String:Dictionary[String:String]]
In an extension I want to loop through all the values (Dictionary[String:String] and retrieve one of the values.
So I wrote this:
for dictNEO in Array(self.values) {
print(dictNEO)
print(type(of: dictNEO))
print(dictNEO["approachDate"])
}
and am getting this error on the last print line: Value of type 'Value' has no subscripts
Here's the first two print lines:
["nominalDist": "\"13.58 ", "approachDate": "\"2020-Feb-01 08:18 ± < 00:01\"", "minimumDist": "\"13.58 ", "diameter": "\"92 m - 210 m\"", "name": "\"(2017 AE5)\""]
Dictionary<String, String>
So I am confused as to why it is telling me it has no subscripts when it sees the type of as a Dictionary.
You have written this as an extension to Dictionary if I understand you correctly and that means that self is generic and defined as Dictionary<Key, Value> and not to you specific type so in your for loop you are looping over an array of [Value].
So you need to typecast Value before accessing it as a dictionary
if let dictionary = dictNEO as? [String: String] {
print(dictNEO["approachDate"])
}
but since it makes little sense to have an extension to Dictionary where you access a specific key it would be better to write it as a function. Since the dictionary is well defined now there is no issue with the last print
func printValuesForSubKey(_ key: String, _ dict: [String: [String: String]]) {
for (dictNEO) in dict.values {
print(dictNEO)
print(type(of: dictNEO))
print(dictNEO[key])
}
}
Note, I don't have an explanation why type(of:) recognises it as [String: String]
The code snippet doesn't work because values property is a collection of collections and with Array(values) you create a collection of collection of collections. In short, instead going down the code goes up and creates new collection level.
Solution with a Higher order function map:
self.values.map { print(type(of: $0)); $0["approachDate"] }
Solution with For-In Loop
for dictNEO in self.values {
print(dictNEO)
print(type(of: dictNEO))
print(dictNEO["approachDate"])
}

Override Swift dictionary creation to not allow nil

In this playground example I'm hoping to find an extension that will remove any nils I put into the creation of a dictionary.
var someValue: String?
if false {
someValue = "test"
}
var dict = ["key": "value",
"key2": someValue]
print("\(dict)")
dict["key3"] = nil
print("\(dict)")
In the above code the current log is
[AnyHashable("key2"): nil, AnyHashable("key"): Optional("value")]
[AnyHashable("key2"): nil, AnyHashable("key"): Optional("value")]
key3 is never added because setting = nil tells it to be removed. I would like to add that functionality to the initial creation of the dictionary but have yet to find a solution that works.
A working solution would result in the following print out
[AnyHashable("key"): Optional("value")]
[AnyHashable("key"): Optional("value")]
This is not solvable in Swift. The correct way to write it is:
var dict = ["key": "value"]
if false {
dict["key2"] = "test"
}
Swift doesn't provide the kind of syntax you're describing, and trying to force it to is going to break the type and create buggy situations. Do not try to create [AnyHashable: Any?]. That is a completely broken type that's going to burn you (Any? is completely broken as a type because Optional is Any, and anything can implicitly become Optional, so it becomes a bizarre recursive rabbit hole). [AnyHashable: Any] is acceptable if you must bridge to NSDictionary, but in general it should be strongly avoided and limited to just where you need it.
Note that this was much even more broken in ObjC (you could write this kind of stuff, but then it'd crash or truncate your dictionary, or some other weird bug), so at least we're making some progress.
In a lot of cases when I see people run into this problem, it's because they've overused optionals in the first place. In your user.name example, why is name optional in the first place? Is there any difference between nil and ""? If there isn't (and there usually isn't), then just make name non-optional (nonnullable in ObjC) and default it to empty and lots of problems go away. Having two versions of the same value (i.e. nil and "" have the same meaning) indicates a type problem, not a syntax problem.
If you want to simplify the syntax just a little bit with a quick extension that works on key/value? pairs like this:
extension Dictionary {
init(keyOptionalPairs: [(Key, Value?)]) {
var d: [Key: Value] = [:]
for (key, value) in keyOptionalPairs {
d[key] = value
}
self = d
}
}
let keyValues: [(String, String?)] = [
("key", "value"),
("key2", nil)
]
let dict = Dictionary(keyOptionalPairs: keyValues)
But notice that the Dictionary is [String: String], not [String: String?]. That's on purpose.
Compared to your syntax, it just adds a set of parens. But compare to the non-fancy version, which isn't beautiful, but is very straightforward.
let dict: [String: String] = {
var d: [String: String] = [:]
d["key"] = "value"
d["key2"] = nil
return d
}()
In Swift The Programming Language Book, I quote:
You can use subscript syntax to remove a key-value pair from a dictionary by assigning a value of nil for that key
They said that assigning a value to nil while remove the pair, but it is not mentioned while initializing the dictionary, so I think it is not valid.
If you really need to do that, I suggest to do some logic after initializing the dictionary to do that for you, like that:
for (key, value) in dict {
if value == nil {
dict[key] = nil
}
}

Setter for dictionary property - OR: get last added item from dictionary

I have a custom class with different computed properties. One of them is a Dictionary of [String: String]. The getter is no problem, but I don't know how to use the setter: How can I figure out, what was the last value added to the dictionary? Obviously newValue.last doesn't exists (.first does!).
EDIT:
This seems to work:
var myProp: [String: String] {
get { ... }
set {
let lastVal = newValue[newValue.startIndex.advancedBy(newValue.count-1)]
...
}
BUT: will this always return the last added value?
EDIT 2
The first edit is wrong. A dictionary is unordered and with this way it's not sure, if it really returns the last added key and value. See my answer below.
As you point out, a Dictionary is an unorderd collection of key-value pairs, so there is no last getter (first is just a convenience for what in Objective-C was more appropriately called anyObject) . The Dictionary also does not keep track of the order items were added.
To get the last item, there are two possibilities. You could refactor to use an array, e.g. of tuples (key, value); or you could keep track of the last item added in a separate variable.
But maybe there is a misunderstanding about the "setter". A setter sets the entire object.
set { myProp = newValue }
So if you have a myProp = ["foo": "bar"], the entire dictionary in myProp is overwritten with this data.
What you want is to add a key to the property. In Swift, this is done by subscripting.
myProp["foo"] = "bar"
You do not have to implement anything special in the get closure.
Note that you have to remember two things, though: first, the dictionary has to be properly initialized; second, any existing item will be overwritten if the new value uses the identical key.
I understand now... the dictionary is unordered. To really get the last added value, I have to compare the value itself with the newValue. The working code:
var myProp: [String: String] {
get { // doing things to read the things and add them to a dictionary }
set {
var new = newValue
for (key, value) in myProp {
if new[key] == value {
new.removeValueForKey(key)
}
}
// now 'new' should only have one key and one value, that one, that just was added
}
}

(String: AnyObject) does not have a member named 'subscript'

I've been through similar questions but still do not understand why my code is throwing an error.
var dict = [String:AnyObject]()
dict["participants"] = ["foo", "bar"]
dict["participants"][0] = "baz"
The error is on line 3: (String: AnyObject) does not have a member named 'subscript'
I'm setting the participants key to an array and then trying to update the first element of it without any luck. The code above is shortened for example purposes, but I am using [String:AnyObject] because it is not only arrays that are stored in the dictionary.
It's probably something really trivial but I am still new to Swift. Thanks for any help in advance!
The error message tells you exactly what the problem is. Your dictionary values are typed as AnyObject. I know you know that this value is a string array, but Swift does not know that; it knows only what you told it, that this is an AnyObject. But AnyObject can't be subscripted (in fact, you can't do much with it at all). If you want to use subscripting, you need to tell Swift that this is not an AnyObject but rather an Array of some sort (here, an array of String).
There is then a second problem, which is that dict["participants"] is not in fact even an AnyObject - it is an Optional wrapping an AnyObject. So you will have to unwrap it and cast it in order to subscript it.
There is then a third problem, which is that you can't mutate an array value inside a dictionary in place. You will have to extract the value, mutate it, and then replace it.
So, your entire code will look like this:
var dict = [String:AnyObject]()
dict["participants"] = ["foo", "bar"]
var arr = dict["participants"] as [String] // unwrap the optional and cast
arr[0] = "baz" // now we can subscript!
dict["participants"] = arr // but now we have to write back into the dict
Extra for experts: If you want to be disgustingly cool and Swifty (and who doesn't??), you can perform the mutation and the assignment in one move by using a define-and-call anonymous function, like this:
var dict = [String:AnyObject]()
dict["participants"] = ["foo", "bar"]
dict["participants"] = {
var arr = dict["participants"] as [String]
arr[0] = "baz"
return arr
}()