Casting concrete type to Any? - ios

I have a use case where I want to assign an array of objects of a specific type to a variable of type [Any]. Considering these references:
var genericArray : [Any]
let specificArray : [MyClass]
This throws a compiler error:
genericArray = specificArray
//Cannot assign value of type '[MyClass]' to value of type '[Any]'
And so does this:
genericArray = specificArray as [Any]
//'Any' is not a subtype of 'MyClass'
This works:
genericArray = specificArray.map { $0 }
But it seems less than elegant. Is there a better way to handle this assignment?

If your genericArray hold instances of class types only (not including function types) then just use var genericArray : [AnyObject]. That will work because specificArray type is set to MyClass which is class I guess

Related

Can't perform methods of objects stored in Array[Any]

I want to store objects of different types in an array.
The program below is only a minimum demo. In the anyArray:[Any] an instance of Object1 is stored. The print statement prints out the expected object type. In the following line the test of the stored object's type returns true. This means, during run time the correct object type is known and every thing seems to be fine.
class Object1 {
var name = "Object1"
}
var anyArray:[Any] = [Object1()]
print("\(type(of: anyArray[0]))")
let testResult = anyArray[0] is Object1
print("Test result:\(testResult)")
//print("Name:\((anyArray[0]).name)")
Console output:
Object1
Test result:true
However, if I try to print out the name property of the object, I get an error message from the editor:
Value of type 'Any' has no member 'name'
Well, at compile time the object's type is unknown. That's why the compiler complains. How can I tell the compiler that it is OK to access the properties of the stored object?
The difference comes from the difference from Type Checking in:
runtime, or
compile time
The is operator checks at runtime whether the expression can be cast to the specified type. type(of:) checks, at runtime, the exact type, without consideration for subclasses.
anyArray[0].name doesn't compile since the Type Any doesn't have a name property.
If you're sure anyArray[0] is an Object1, you could use the downcast operator as!:
print("\((anyArray[0] as! Object1).name)")
To check at runtime if an element from anyArray could be an Object1 use optional binding, using the conditional casting operator as?:
if let:
if let object = anyArray[0] as? Object1 {
print(object.name)
}
Or use the guard statement, if you want to use that object in the rest of the scope:
guard let object = anyArray[0] as? Object1 else {
fatalError("The first element is not an Object1")
}
print(object.name)
If all objects in your array have a name property, and you don't want to go through all the hoops of optional binding repeatedly, then use a protocol. Your code will look like this:
protocol Named {
var name: String {get set}
}
class Object1: Named {
var name = "Object1"
}
var anyArray:[Named] = [Object1()]
print("\(type(of: anyArray[0]))")
let testResult = anyArray[0] is Object1
print("Test result:\(testResult)")
print("Name:\(anyArray[0].name)")
Notice that anyArray is now an array of Named objects, and that Object1 conforms to the Named protocol.
To learn more about protocols, have a look here.
You object is still of type Any. You just checked if it can be of type Object1, but you did not cast it. If you want the object as Object1, you need to cast it.
Also if multiple classes can have name, you need to use Protocol like #vadian has mentioned in his comment and cast it to that protocol.
protocol NameProtocol {
var name: String {get set}
}
class Object1: NameProtocol {
var name = "Object1"
}
if let testResult = anyArray[0] as? NameProtocol {
print(testResult.name)
}
Edit: "I want to store objects of different types in an array". The solution that you have marked as correct will not work if all the objects that you have do not conform to the protocol.

Swift compile time errors when using generics and accessing dictionary's values

Below I have a class, B, with a generic type and the generic type has a subclass type constraint. In a separate class, A, I create a dictionary property, values, with the key as String and value as B. Class A then has methods to return or set values in the dictionary such that the values are not constrained to a single type (they maintain their generic SomeType type which is a subclass of NSObject). However, this produces the following two errors noted inline below:
class A: NSObject {
var values = [String : B]()
func get<SomeType: NSObject>(key: String) -> B<SomeType>? {
// #1 Error on line below: cannot convert return expression of type 'B<NSObject>?' to return type 'B<SomeType>?'
return values[key]
}
func set<SomeType: NSObject>(key: String, value: B<SomeType>) {
// #2 Error on line below: cannot assign value of type 'B<SomeType>' to type 'B<NSObject>?'
values[key] = value
}
}
class B<SomeType: NSObject>: NSObject {
}
I've attempted various forms of declaring the values dictionary to tell the compiler that SomeType is a subclass of NSObject and everything is going to be ok, but have been unsuccessful. Similarly to this question, I'm a bit stumped because the methods define SomeType as a subclass of NSObject and therefore things appear to be type safe when setting and getting from values.
I could remove the generic types from the methods and instead force the type to be <NSObject>, but then I'd run into the same problem as noted here.
This may not be doing what you think it's doing:
var values = [String : B]()
This isn't really [String : B], it's [String : B<NSObject>]. (I'm actually kind of surprised that this is legal syntax; I'd be tempted to open a bugreport about that; B isn't a proper type in Swift.) You may realize this already, but it's the first important note.
The second important note is that generic types are not covariant in Swift. A Thing<Cat> is not a subtype of Thing<Animal>. There are some type-theory reasons for this, and there are some technical implementation reasons for this, but the important fact is that it's not possible in Swift.
If you need to hold a variety of B types, then you'll need to build a type eraser. In your case, the type eraser could possibly be B<NSObject> with something like this:
class B<SomeType: NSObject>: NSObject {
let value: SomeType
init(value: SomeType) {
self.value = value
}
func liftGeneric() -> B<NSObject> {
return B<NSObject>(value: value)
}
}
If you need to hold just one kind of B type, then make A generic as well.

Argument type '[String?]' does not conform to expected type 'AnyObject'

Code:
var contactArray = [nameField.text, addressField.text, phoneField.text]
NSKeyedArchiver.archiveRootObject(contactArray, toFile: dataFilePath!)
//Error on contactArray: Argument type '[String?]' does not conform to expected type 'AnyObject'
Since the contactArray is a non optional value, I can't force unwrap it, what should I do?
You are correct that contactArray is not an optional; it's an array of optionals. You need to unwrap each individual element of the array as you construct it, e.g.:
var contactArray = [nameField.text!, addressField.text!, phoneField.text!]
Also, unless you plan on modifying that array later, you should use let instead of var to make sure it can't be modified.
AnyObject can only be used for classes
Therefore :
var contactArray : NSArray = [nameField.text, addressField.text, phoneField.text];
just make your array type NSArray

Cannot subscript a value of type '[NSObject : AnyObject]?' with an index of type 'String'

ERROR:
Cannot subscript a value of type '[NSObject : AnyObject]?' with an
index of type 'String'
CODE:
func getApple(appleId: String) {
var apples = userDefaults.dictionaryForKey("apples_array")
println(apples[appleId])
Should be:
var apples = userDefaults.dictionaryForKey("apples_array")
println(apples?[appleId])
The issue here is that type [NSObject : AnyObject]? implies an optional type, which means you're attempting to call a subscript on what is essentially an enum. When you try to do this, there's no subscript declared, so the system chokes.
By adding the ? we're saying, unwrap this value if possible, and then call the subscript. This way the system infers to look on type [NSObject : AnyObject] for subscript declarations and everything is ok.
You could also use ! to force an unwrap, but this will crash if apples is nil. Another possible way to write this would be:
let apples = userDefaults.dictionaryForKey("apples_array") ?? [:]
println(apples[appleId])
This way, apples is no longer optional and it will always have the subscript syntax. No unwrapping necessary.
I think it is far more clear to use optional binding so that the println is only invoked when there is an actual value to print
func getApple(appleId: String) {
if let apples = userDefaults.dictionaryForKey("apples_array") {
println(apples[appleId])
}
}

Cannot assign a value of type '[Thing]' to a value of type '[Any]'

I'm struggling with what seems like a simple Swift problem.
I've declared a struct with a static function that returns some instances:
struct Thing {
static func allTheThings() -> [Thing] {
...
}
}
I've got a CustomViewController with an property declared:
var objects = [Any]()
In the subclass of that controller in viewDidLoad(), I'm trying to assign the objects property.
objects = Thing.allTheThings()
But I'm getting a compiler error
Cannot assign a value of type '[Thing]' to a value of type '[Any]'
Isn't that the whole point of Any?
This works:
objects = Thing.allTheThings().map { $0 }
But this doesn't
let things = Thing.allTheThings().map { $0 }
objects = things
Any ideas what's going on here?
It seems Swift can convert Thing to Any but not [Thing] to [Any].
The reason this works
objects = Thing.allTheThings().map { $0 }
is that the compiler can infer that the type of $0 is Any, but in the second example
let things = Thing.allTheThings().map { $0 }
it infers $0 to be of type Thing. You end up with the variable things being of type [Thing], which means that the assignment
objects = things
will mean a conversion from [Thing] to [Any] which does not work.

Resources