I see it is possible to create dictionaries in swift as follows
var dict1 = [String: Int]()
var dict2 = Dictionary<String, Int>()
and then use it as normal. When should I use which method of creating dictionaries?
The Apple produced book "The Swift Programming Language" says
The type of a Swift dictionary is written in full as Dictionary<Key, Value>, where Key is the type of value that can be used as a dictionary key, and Value is the type of value that the dictionary stores for those keys.
You can also write the type of a dictionary in shorthand form as [Key: Value]. Although the two forms are functionally identical, the shorthand form is preferred and is used throughout this guide when referring to the type of a dictionary.
so it seems that the shorthand form is preferred. Probably because it looks a bit cleaner.
Related
I have a struct which I am initializing, with a dictionary. The values in this dictionary are of many different types: String, Int and Dictionary. This embedded dictionary again, also has many different types (String, Int, Bool, Array and Dictionary). I know, it's a mess. So, I am trying to find the correct syntax for the initializer of this struct which takes as input this big dictionary.
I tried this, but it doesn't satisfy most of my elements.
init(dictionary: [AnyHashable: [AnyHashable: [AnyHashable: Any]]){
...
}
For instance, when trying to access a String on the dictionary's first level, the compiler is expecting a [AnyHashable: [AnyHashable: Any]] type object.
Is there a general way of declaring the type of this dictionary in this initializer? I want to be able to access values and either initialize variables with them or use these values as dictionaries. Is there a way to do that in Swift?
Complementary information: I'm extracting information from my FireStore DB, and this is why I have this heavily layered dictionary. FireStore documents can handle many different levels of information and I had to fill mine up for design purposes.
Thank you!
For instance, when trying to access a String on the dictionary's first level, the compiler is expecting a [String: [String: Any]] type object.
Indeed, because by writing dictionary: [String: [String: [String: Any]], you made a guarantee to the compiler that the values of dictionary are [String: [String: Any]].
If you want your dictionary to be able to hold many different types at runtime, you'll need to give it a static type that is a supertype (protocol or superclass) of all the types you want to use. There's two ways to use such values:
You make the protocol or superclass contain the functions (and properties, subscripts, etc). that you want to use. You can call these functions on the values without needing to know the concrete types of the values. The compiler knows that all values in the dict will support those functions (by virtue of being a subclass of the class, or conforming to the protocol).
Downcast the values (for example, you know that the value for the key "age" will always be an Int) at runtime, into a more specific type that does what you want.
The most general, universally applicable type is Any. But it's generality comes from it not having any requirements, so you really can't do anything with an Any, besides pass it around, ultimately having to downcast it to do anything with it.
TL;DR: You have to use Any, or perhaps some other general protocol type or superclass
Better yet, there's a bunch of libraries for adding Codable functionality to Firebase. I suggest you use one of them.
I have seen many examples of how to get a value from a plist in swift - however all involve type casting the variable after it is read from the plist. Since we already give the property a type when creating it in the plist, why do we have to type it again? Is there any way to read values from the plist with the type they have in the plist?
For example this is the approach I am using to read the value from a plist:
extension Bundle {
var someVar: String {
return object(forInfoDictionaryKey: "someVar") as? String ?? ""
}
}
Why do I need to cast the variable again as String when I have already typed it as String in the plist?
Because your plist is a [String: Any] dictionary. It can hold values that are not String and the compiler can't know what is what until you specifically tell it - via the casting you are doing.
Please ⌥-click on object(forInfoDictionaryKey. You will see
Since a property list can contain many different types (array, dictionary, string, url, numeric values, date, data) the return value is optional Any and you have to cast the type.
Apple provides another approach in URL: If you get a value from resourceValues(forKeys with an URLResourceKey you get also the expected type.
Feel free to write an extension of Bundle which considers also the actual type.
I just want to know the benefits of Key-Value Coding in Swift 4. As I am well aware of key-value coding (hold reference to properties without actually accessing the underlying data held by the property).
For Example:
struct Student {
var name: String?
var rollNo: String?
}
let student = Student(name: "aman", rollNo: "12121")
let nameKey = \Student.name
let name = student[keyPath: nameKey]
Here we created the the instance of Student and access the value by subscript (keyPath) but I can easily access the value by simply writing the code student.name.
In Objective-C, we use string as a key like given below and we could take some benefits from it
[object valueForKey:#"name"]
My question is that is there any benefits related to coding level or memory level?
Two key advantages are:
Keys are validated: Xcode will warn you, at compile time, if you try to use a key that is not valid.
Results are strongly typed: With the following, the compiler will know that the resulting name is a String:
let name = student[keyPath: nameKey]
This eliminates the need to do any casting.
The result is that it's much easier to write safe code.
I want to pass an optional dictionary parameter in Swift function, I tried declaring the function as
func myFunction(_ type:Int, params:Dictionary<String,Any?>?)
and also
func myFunc(_ type:Int, params:[String:Any?]?)
I get warning "Expression implicitly coerced from Any? to Any" with first declaration when I try to pass a dictionary but not with second. I need to know what is the difference between the two and why the warning. FYI, here is how I pass the dictionary in both cases:
myFunc(1, params:["key1": value1, "key2": value2])
According to Swift documentation, they are identical:
The type of a Swift dictionary is written in full as Dictionary<Key,
Value>, where Key is the type of value that can be used as a
dictionary key, and Value is the type of value that the dictionary
stores for those keys.
You can also write the type of a dictionary in shorthand form as [Key:
Value]. Although the two forms are functionally identical, the
shorthand form is preferred and is used throughout this guide when
referring to the type of a dictionary.
I'm using Xcode Version 8.0 and i couldn't regenerate your issue.
They are just different syntactic sugar for dictionary declaration and one type will not work in all cases which are kind of a bug.
For Example,
var msSet = [Vertex<Int> : Double]()
I have asked a question related to it.
Swift Dictionary Initialization of custom type gives: '>' is not a postfix unary operator error
A fairly simple piece of code
var dict: [String: AnyObject] = [:]
dict["key"] = "value"
generates the following compile-time error
Cannot assign value of type 'String' to type 'AnyObject?'
Simple type checks tell me that String is AnyObject
"value" is AnyObject // returns true
I could change AnyObject to Any and everything would work
var dict: [String: Any] = [:]
dict["key"] = "value"
but I want to understand why do I get the error? Is String no longer AnyObject? Or is this a bug?
In b6, String no longer magically bridges to NSString. String is not a class; it's a struct. You need to do the bridging by hand:
dict["key"] = "value" as AnyObject
The fact that is still seems to be bridging is likely a bug and should be reported.
It goes without saying that [String: AnyObject] and [String: Any] should be used as little as possible in your code.
(Make sure to follow the link Hamish provides in the comments below.)
I'll complement to #RobNapier's answer with some official sources.
The removal of the implicit bridging mechanisms was accepted in the following Swift evolution proposal, to be implemented for Swift 3
SE-0072: Fully eliminate implicit bridging conversions from Swift
Previously, implicit conversions were available from some Swift native types to associated Objective-C types (Swift types conforming to private protocol _ObjectiveCBridgeable, e.g. natively Int, String, )
For this reason, we decided to make a compromise. We would require
explicit bridging casts when converting from a bridged Objective-C
type to its associated Swift value type (E.g., NSString -> String),
but not the other way around.
... [From SE-0072]
With Swift 3, such implicit conversion mechanisms will no longer be available.
With the introduction of Objective-C generics last year, along with
all of the awesome improvements to API importing happening for Swift
3, I think it’s time that we take another look at completing this
work.
...
I propose that we fully eliminate implicit bridging conversions in
Swift 3. This would mean that some users might have to introduce
more explicit casts in their code, but we would remove another special
case from Swift's type system and be able to further simplify the
compiler.
...
Code that previously relied on implicit conversions between Swift
value types and their associated bridged Objective-C type will now
require a manual coercion via an as cast.
Finally, the release notes for Xcode 8 beta 6 (login required) states that this proposal has now been implemented for beta 6:
New in Xcode 8 beta 6 - Swift Compiler: Swift Language
...
Bridging conversions are no longer implicit. The conversion from a Swift value type to its corresponding object can be forced with as.
For example: string as NSString. Any Swift value can also be
converted to its boxed id representation with as AnyObject.
(SE-0072)
W.r.t. new "boxed id" allowing explicit conversion for any Swift value to AnyObject, see e.g. the following thread:
AnyObject not working in Xcode8 beta6?