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

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

Related

Array keeps coming back as Nil from Parse

I'm trying to determine if a Parse value has been defined and then if the object has a value assign the Array to my arr variable.
But it keeps coming back as nil. And yes, there is a value inside blockedUsers which is a Parse Array.
if let blockedUser2 = currentUser?["blockedUsers"] as! [String]{
let arr = currentUser?["blockedUsers"] as! [String]
print(arr)
}
You don't need arr: if the condition is matched, you can access blockedUser2. You are already unwrapping it safely with the if let declaration.
About your issue... could you provide some more code? Where do you get your currentUser array from?

Immutable value error when appending to array within dictionary after downcasting

var someDict = [String:Any]()
someDict["foo"] = ["hello"]
(someDict["foo"] as? [String])?.append("goodbye") // error here
I am trying to add a value to an existing dictionary containing an array. The dictionary also contains other non-array values, so it has to have value type Any. The problem is that, when I do this, I get an error Cannot use mutating member on immutable value of type '[String]'. Some Googling turned up a few references such as this suggesting that arrays within dictionaries are always immutable, but the compiler doesn't complain if I do this:
var someDict = [String:[String]]()
someDict["foo"] = ["hello"]
someDict["foo"]?.append("goodbye")
so I suspect that information is outdated and it's something specific to the downcasting. Is there any way I can get around this without copying and re-assigning the entire dictionary value?
Yes, it is related the the downcasting. Try this instead:
var someDict = [String:Any]()
someDict["foo"] = ["hello"]
if var arr = someDict["foo"] as? [String] {
arr.append("goodbye")
someDict["foo"] = arr
}

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

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

Swift various ways of creating empty array what are differences between them?

I looked through docs some forums and found I can create an array in various ways. I am confused which one should we be using?
var testArray = [Int]()
testArray.append(1)
var anotherTestArray: [Int] = []
anotherTestArray.append(1)
var yetAnotherTestArray: Array<Int> = []
yetAnotherTestArray.append(1)
var yetYetYetAnotherTestArray = Array<Int>()
yetYetYetAnotherTestArray.append(1)
This is not empty array but It keeps it's type for each element to be strictly to an Int
var yetYetAnotherTestArray = [1]
I think the cleanest way to create an array is var testArray: [Int] = []
In swift array objects must be the same type. If you want to store different objects of different types use [AnyObject]. However, you should always know what is coming out of it. Since the returning type value will be AnyObject, you have to cast down the value to the type you want.
I really don't recommend using AnyObject as the type of your arrays unless you really know what you are doing.
Here is an example anyway:
let a: [AnyObject] = [1, "a"]
let b = a[0] // An Int
let c = a[1] // A String