Iterating dictionary swift 3 - swift

I have below code in my project.
for (key, value) in photoDic {
if let url = URL.init(string: value as! String){
let photo : PhotoRecord = PhotoRecord.init(name:key as! String, url:url)
self.photoRecords.append(photo)
}
}
My question is how can I make key and value in for loop optional, or check if either of them are nil?
I am not able to check if they are nil, getting warning saying any cannot be nil because it is nonoptional.
I was thinking of using something like
for(key:String?, value:String?){}
But it is not working.

The key in a dictionary can't be an optional. (The key must conform to the Hashable protocol, and optionals don't.) So you CAN'T make the keys in your dictionary optional
If you want the values of your dictionary to be Optionals then you need to declare them as Optionals.
So, for example, change
let photoDic: [String: String] = ["key1": "http://www.someDomain.com/image.jpg"]
to
let photoDic: [String: String?] = ["key1": "http://www.someDomain.com/image.jpg"]
(Note that the type of photoDic is changed to [String: String?].)

As mentioned already all keys in a dictionary are non-optional by definition.
Further in NSDictionary all values are non-optional by definition, too.
Be happy about that because
There is no need to check for nil.
The code will never crash.
A Swift dictionary can theoretically contain optional values but practically you are discouraged from using it. For compatibility reasons to NSDictionary a nil value indicates key is missing.

Related

Confused on syntax of ! operator in this code

I have a post request. I am working with the response that is stored in obj.
The following is declared at the top var account = Account(), which Account() is a dictionary initialized class.
var obj = response.result.value as? [String: Any]
if response.response?.statusCode == 200 {
let accounts = obj!["accounts"] as! [[String: Any]]
accounts.forEach { a in
let acc = Account(dic: a)
self.account = acc
}
}
I am very confused about this syntax I don't understand yet:
let accounts = obj!["accounts"] as! [[String: Any]]
I just knows this allows me to store the response into self.account which I can use that data to populate my view. Can someone help elaborate on what is exactly happening here?
Thank you.
The ! does a different thing when it appears in obj! than when it appears in as!.
In obj!, the code is force unwrapping (or unconditionally unwrapping) the obj variable. This implies that obj is of an optional type; we can confirm that by looking at the declaration of obj:
var obj = response.result.value as? [String: Any]
When the as operator is postfixed with a ?, the returned value might be nil and thus the resulting type is an optional.
Contrast this to the second instance of a ! in your program:
let accounts = obj!["accounts"] as! [[String: Any]]
In this instance, the as operator is performing a forced conversion; that means that the conversion will always result in a non-optional type, but it may cause a runtime error, as a result of being unable to fallback to nil.
let accounts = obj!["accounts"] as! [[String: Any]]
Let's decompose that line into smaller "chunks":
obj! // Force unwraps the object "obj" (which is a dictionary) (will crash if "obj" is nil
obj!["accounts"] // After being unwrapped tries to access the key "accounts" inside the dictionary
as! [[String: Any]]// Is going to "cast" or treat the above dictionary as an Array of Dictionaries of String: Any (again forcing it and crash if it cannot)
But remember!
It's not a good practice to force unwrap things as your app might crash. You should always go for optional bindings or optional chaining.

How would you use .updateValue() to add a sub-dictionary to a UserDefaults dictionary?

Is this proper syntax for this line of code? If not what would be the correct syntax and why so?
UserDefaults.standard.dictionary(forKey: "mainDict")?.updateValue(subDict, forKey: "subDictTitle")
First, you have to store Userdefault dictionary to a temporary dictionary. Then you have to add data to a temporary dictionary.
No need to update the dictionary to Userdefault. When you store Dictionary to the Usedefault with the same key, it will replace the older dictionary to the new one.
UserDefaults.standard.set(YOUR_TEMPORARY_DICTIONARY, forKey: YOUR_KEY_NAME)
The updateValue(_:forKey:) is a mutating instance method for the dictionary, which means that it updates the value of the dictionary. Obviously, In order to mutate an instance, it has to be mutable, which is not the case when calling UserDefaults.standard.dictionary(forKey: "mainDict").
Even if you did:
let myDict = ["k1": "Hello"]
UserDefaults.standard.register(defaults: ["myDict": myDict])
var mutable = UserDefaults.standard.dictionary(forKey: "myDict")!
mutable["k1"] = "HEY"
print(UserDefaults.standard.dictionary(forKey: "myDict")) // Optional(["k1": Hello])
the value of the dictionary set in the user default won't change because simply mutable is a copy of it.
To clarify, it's similar to implementing:
UserDefaults.standard.register(defaults: ["k2": "this is my string"])
UserDefaults.standard.string(forKey: "k2") = "new string"
which generates the error of
Expression is not assignable: function call returns immutable value
So, in order to resolve this issue, what you should do is to set a new value (updated dictionary) to the user defaults with the same key:
var myDict = UserDefaults.standard.dictionary(forKey: "myDict")
myDict?.updateValue("Hey", forKey: "k1")
UserDefaults.standard.set(myDict, forKey: "myDict")

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
}
}

Why aren't [String:AnyObject?] and [String:AnyObject] the same type to the swift compiler?

Semantically speaking, [String:AnyObject?] and [String:AnyObject] are the same thing in terms of the way they act, meaning they will return the same thing if I access a key that wasn't set and setting a key to nil will remove that key from the dictionary. Why aren't they considered the same type?
EDIT: I understand the difference from the compiler point of view from the answers. I decided to put the following code in the playground:
var optional = [String:AnyObject?]()
var regular = [String:AnyObject]()
//Some control keys
optional["controlkey"] = "valueoptional"
regular["controlkey"] = "valueRegular"
//Set the keys
optional["keyOptional"] = "valueoptional"
regular["keyRegular"] = "valueRegular"
//Unset the keys
optional["keyOptional"] = nil
regular["keyRegular"] = nil
for (key,val) in optional {
print("key: \(key)\tval: \(val)")
}
for (key,val) in regular {
print("key: \(key)\tval: \(val)")
}
To my surprise the optional did not print the key that was set to nil.
Output was as follow:
key: controlkey val: Optional(valueoptional) //The keys for the optional dictionary
key: controlkey val: valueRegular //The keys for the `regular` dictionary
Why doesn't the key that I set to nil show up?
From the Apple docs:
The Swift language defines the postfix ? as syntactic sugar for the
named type Optional, which is defined in the Swift standard
library.
The type AnyObject? is an enumeration with two cases, None and Some(Wrapped), which are used to represent values that may or may not be present. But AnyObject is responding to one that will be presented.
Setting nil for a key in dictionary means removing the element itself.
Try to set NSNull()
optional["keyOptional"] = NSNull()
Because a nullable type and a non nullable type aren't the same thing from the compiler point of view. It just happens that the Dictionnary interface eventually "blend them" in similar entity, but this is linked with the Dictionnary implementation, not really with the type system.
EDIT: Your update changes the context of the question, but setting to nil is a way to unset from my understanding.
They are different because you can actually store nil into [String:AnyObject?]. You cannot do it using optional[key] = value (subscript operator) because that one has a special behavior for nil (removes value) but you can do it:
var optional: [String:AnyObject?] = ["test": nil]
optional.updateValue(nil, forKey: "test2")
print(optional) // ["test2": nil, "test": nil]
Of course, getting a value from such a dictionary:
print(optional["test"]) // Optional(nil)
results in a double optional Optional<Optional<AnyObject>> (or AnyObject??) and interaction with such types is cumbersome so you should avoid storing nil into dictionaries.
Because setting a key to nil remove that key from the dictionary! From the documentation
Reading a key that is not present in self yields nil. Writing nil as the value for a given key erases that key from self
You can't store nil as a dictionary value. If you need something denote nothingness, use NSNull:
optional["keyOptional"] = NSNull()
regular["keyRegular"] = NSNull()
They aren't the same type because they aren't the same type ;-) Compilers are such literal creatures.
#Sulthan's answer is correct and more complete - I didn't see it until after I hit post on my own answer. But if you look up a key in a [String: AnyObject?] dictionary and a value is present, you'll get an optional wrapped in an optional - the result is an AnyObject??. You'd have to unwrap it twice to use it:
var optional = [String: AnyObject?]()
// I changed it to NSString because String isn't an AnyObject
v
optional["controlkey"] = NSString(string: "valueoptional")
print(optional["controlkey"]) // prints "Optional(Optional(valueoptional))"
if let val = optional["controlkey"] {
print(val) // prints "Optional(valueoptional)"
if let unwrappedVal = val {
print(unwrappedVal) // prints "valueoptional"
}
}

Chaining Optionals in Swift

Up until now, I've been unwrapping Optionals in Swift 2.1 like so:
#IBOutlet var commentTextView: UITextView!
if let comment = user["comment"] as? String {
commentTextView.text = comment
}
I never really thought about it, but I think the reason I was doing this was because I was worried that this statement would throw an error if user["comment"] returned something other than a String:
commentTextView.text = user["comment"] as? String
If user["comment"] isn't a String, will the variable on the left of the assignment operator be assigned and throw an error or will the assignment be skipped?
I guess user is in fact a dictionary [String: Any] and what you really do with if let comment = user["comment"] as? String { ... } is not just unwrapping the optional but a conditional type casting (and then unwrapping an optional result of it):
Use the conditional form of the type cast operator (as?) when you are not sure if the downcast will succeed. This form of the operator will always return an optional value, and the value will be nil if the downcast was not possible. This enables you to check for a successful downcast.
Now, to answer your question, if user["comment"] isn't a String then the result will be that commentTextView.text will be assigned nil value, which is bad because its type is String! (implicitly unwrapped optional) about which we hold a promise that it will never be nil. So, yes, there will be an error, an exception actually, but not at the place you would like it to be but at the moment your application will try to access its value assuming that it's not going to be nil.
What you should really do depends on a particular case.
E.g. if you can make user to be a dictionary like [String: String], then you would be able to truly get to unwrapping the optionals and use something like if let comment = user["comment"] { ... }. Or, if you are totally sure that the value for "comment" key will always be there, then you could just do let comment = user["comment"]!.
But if that's not possible then you have to stick with down-casting and the only other thing you can do is to use forced form of it, that is commentTextView.text = user["comment"] as! String. This one at least will produce an exception right at the spot in case if the value at "comment" happens to be not a String but something else.
nil will be assigned to the variable.
If the type of the variable is a non-optional, you'll get a runtime error.
However if user["comment"] is a String you'll get a compiler error about missing ! or ?.
First we need to know of what type the dictionary "user" is.
I assume it is of an unknown type like [String: AnyObject], otherwise why would you try to unwrap it as an String. Let us write a short test to see what happens:
let dict: [String: AnyObject] = ["SomeKey" : 1]
if let y = dict["SomeKey"] as? String {
print(y)
}
You can see clearly that the value of "SomeKey" is an Integer. Trying to unwrap it as an String triggers no error, the "if" statement is just skipped. If an assignment actually happened is hard to prove (maybe by looking at the assembler code) because the variable "y" simply does not exist after the if statement. I assume it will not be created at all.
If the type of the dictionary is known as [String: String] you can omit the try to unwrap it as a String because it's always clear that the type is String.
let dict2: [String: String] = ["SomeKey" : "SomeValue"]
if let y = dict2["WrongKey"] {
// In this case print(y) will not be called because the subscript operator of the dictionary returns nil
print(y)
}
// In this case print(y) will be called because the key is correct
if let y = dict2["SomeKey"] {
print(y)
}