How setting 'nil' as a value of [String: String] dictionary is valid? - swift

I'm curious why this snippet works correctly in Playground:
var dict = [String: String]()
dict["key"] = nil
let value = dict["key"]
we can see that I declared Dictionary with non optional values, after checking it in Playground it works similarly to dictionary declared as [String: String?]
My question is where is the difference in terms of setting nil value between dictionary declared as [String: String] and [String: String?] ?

dict["key"] = nil is a shorthand to removing the key from the dictionary (same as using dict.removeValue(forKey: "key")). If there was a value under the "key" key, after this line the whole entry is removed from the dictionary (both the key and the value).
Read the subscripts docs to learn more:
If you assign nil as the value for the given key, the dictionary removes that key and its associated value.
In the following example, the key-value pair for the key "Aquamarine" is removed from the dictionary by assigning nil to the key-based subscript.
hues["Aquamarine"] = nil
print(hues)
// Prints "["Coral": 18, "Heliotrope": 296, "Cerise": 330]"
let value = dict["key"] gets the value for the key, and by definition returns nil if there is no entry for the given key (which is in your case).
According to docs, subscript returns either the value, or nil, if the key is not in the dictionary:
The value associated with key if key is in the dictionary; otherwise, nil.

dict["key"] = nil means you are removing the item from dictionary. The return type of dict[someKey] is Optional as the key may not be there.

You could consider implementing:
dict["key"] = nil
as the same as calling removeValue(forKey:):
dict.removeValue(forKey: "key")
By default, getting a value from a dictionary would returns an optional value:
var dict = [String: String]()
dict["key"] = "Hello"
let value = dict["key"] // Optional("Hello")
Note that the type of value is optional string (String?).

Related

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")

Iterating dictionary swift 3

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.

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

Why do I still need to unwrap Swift dictionary value?

class X {
static let global: [String:String] = [
"x":"x data",
"y":"y data",
"z":"z data"
]
func test(){
let type = "x"
var data:String = X.global[type]!
}
}
I'm getting the error: Value of optional type 'String?' not unwrapped.
Why do I need to use ! after X.global[type]? I'm not using any optional in my dictionary?
Edited:
Even if X.global[type] may not exist for the type, force unwrapping will still crash on runtime. A better approach may be:
if let valExist = X.global[type] {
}
but Xcode is giving me the wrong idea by hinting about optional type.
Dictionary accessor returns optional of its value type because it does not "know" run-time whether certain key is there in the dictionary or not. If it's present, then the associated value is returned, but if it's not then you get nil.
From the documentation:
You can also use subscript syntax to retrieve a value from the dictionary for a particular key. Because it is possible to request a key for which no value exists, a dictionary’s subscript returns an optional value of the dictionary’s value type. If the dictionary contains a value for the requested key, the subscript returns an optional value containing the existing value for that key. Otherwise, the subscript returns nil...
In order to handle the situation properly you need to unwrap the returned optional.
There are several ways:
Option 1:
func test(){
let type = "x"
if var data = X.global[type] {
// Do something with data
}
}
Option 2:
func test(){
let type = "x"
guard var data = X.global[type] else {
// Handle missing value for "type", then either "return" or "break"
}
// Do something with data
}
Option 3:
func test(){
let type = "x"
var data = X.global[type] ?? "Default value for missing keys"
}
If we look at the Dictionary implementation, subscript is returning a ValueType as optional because it doesn't know if the key is exists or not:
//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`.
subscript (key: KeyType) -> ValueType?
So when we try to get a value from our Dictionary we get it as an optional from the subscript; that is we have to unwrap the optional to get the underlying object. As mentioned in earlier answers, option2 is preferred.
guard var data = X.global[type] else {
//key = 'type' doesn't exists
}
//key exists so do something with 'data'

How to add nil value to Swift Dictionary?

I have made a request to my server in my app. And posted data something like this.Server side is waiting for all parameters even they are nil. But i couldn't add nil values to dictionary.
var postDict = Dictionary<String,AnyObject>
postDict[pass]=123
postDict[name]="ali"
postDict[surname]=nil // dictionary still has only pass and name variables.
Is there a way to add nil value to dictionary ?
How to add nil value to Swift Dictionary?
Basically the same way you add any other value to a dictionary. You first need a dictionary which has a value type that can hold your value. The type AnyObject cannot have a value nil. So a dictionary of type [String : AnyObject] cannot have a value nil.
If you had a dictionary with a value type that was an optional type, like [String : AnyObject?], then it can hold nil values. For example,
let x : [String : AnyObject?] = ["foo" : nil]
If you want to use the subscript syntax to assign an element, it is a little tricky. Note that a subscript of type [K:V] has type V?. The optional is for, when you get it out, indicating whether there is an entry for that key or not, and if so, the value; and when you put it in, it allows you to either set a value or remove the entry (by assigning nil).
That means for our dictionary of type [String : AnyObject?], the subscript has type AnyObject??. Again, when you put a value into the subscript, the "outer" optional allows you to set a value or remove the entry. If we simply wrote
x["foo"] = nil
the compiler infers that to be nil of type AnyObject??, the outer optional, which would mean remove the entry for key "foo".
In order to set the value for key "foo" to the AnyObject? value nil, we need to pass in a non-nil outer optional, containing an inner optional (of type AnyObject?) of value nil. In order to do this, we can do
let v : AnyObject? = nil
x["foo"] = v
or
x["foo"] = nil as AnyObject?
Anything that indicates that we have a nil of AnyObject?, and not AnyObject??.
You can use the updateValue method:
postDict.updateValue(nil, forKey: surname)
As documented in here, setting nil for a key in dictionary means removing the element itself.
If you want null when converting to JSON for example, you can use NSNull()
var postDict = Dictionary<String,AnyObject>()
postDict["pass"]=123
postDict["name"]="ali"
postDict["surname"]=NSNull()
let jsonData = NSJSONSerialization.dataWithJSONObject(postDict, options: NSJSONWritingOptions.allZeros, error: nil)!
let jsonString = NSString(data: jsonData, encoding: NSUTF8StringEncoding)!
// -> {"pass":123,"surname":null,"name":"ali"}
postDict[surname] = Optional<AnyObject>(nil)
You can use the Optional type
var postDict = ["pass": 123, "name": "ali", "surname": Optional()]
Below dictionary will hold one key with nil value
var dict = [String:Any?]()
dict["someKey"] = nil as Any?
To add a nil value to a dictionary in Swift, your dictionary's values must be of the Optional type.
Consider a Person class:
class Person {
let name: String
weak var spouse: Person?
init(name: String, spouse: Person?) {
self.name = name
self.spouse = spouse
}
}
Instances of the Person type can have a name and an optional spouse. Create two instances, and add the first to a dictionary:
let p1 = Person(name: "John", spouse: nil)
let p2 = Person(name: "Doe", spouse: p1)
p1.spouse = p2
var people = [p1.name: p1.spouse]
This dictionary (called people) maps names to spouses, and is of type [String: Person?]. You now have a dictionary with a value of Optional type: Person?.
To update the value of the key p1.name to be nil, use the updateValue(_: forKey:) method on the Dictionary type.
people.updateValue(nil, forKey: p1.name)
people[p1.name]
The value for the key p1.name is now nil. Using updateValue(_: forKey:) is a bit more straightforward in this case because it doesn't involve making a throwaway instance, setting it to nil, and assigning that instance to a key in a dictionary.
NB: See rintaro's answer for inserting null into a post's dictionary.
var dict = [Int:Int?]()
dict[0] = (Int?).none // <--- sets to value nil
dict[0] = nil // <-- removes
dict[0] = .none // <-- same as previous, but more expressive
switch dict[0] {
case .none:
Swift.print("Value does not exist")
case .some(let value):
if let value = value {
Swift.print("Value exists and is", value)
} else {
Swift.print("Value exists and is nil")
}
}
postDict[surname]=nil
When you use subscript to set nil. It deletes the key if exists. In this case key surname will be removed from dictionary if exists.
To set value as nil, there are certain ways.
postDict.updateValue(nil, forKey: surname)
or
let anyObjectNil : AnyObject? = nil
postDict[surname] = anyObjectNil
or
postDict[surname] = nil as AnyObject?