Why must I downcast an array item in Swift, if the sub-class of the array item is known?
> class B {let t: String = "-B-"}
> class B1:B {let t1: String = "-B1-"}
> class B2:B {let t2: String = "-B2-"}
> let bunch = [B(), B1(), B2()]
As expected:
> print(type(of:bunch))
Array<B>
The sub-class of each element of the array is known:
> for item in bunch {print(type(of:item))}
B
B1
B2
Yet (as documented) we cannot access the item's sub-class member, and we have to down-class:
> if bunch[1] is B1 {let b1 = bunch[1] as! B1; print(b1.t1)}
-B1-
because this does not work:
> bunch[1].t1
error: repl.swift:17:6: error: value of type 'B' has no member 't1'; did you mean 't'?
Why can Swift determine the sub-class using type(of:) but it cannot infer it when accessing a member in that sub-class? Is it a bug, a historic hang-over, or am I missing something?
Swift arrays are collections of items of a single type. The type of the array
let bunch = [B(), B1(), B2()]
is inferred as [B] because B is the “nearest common superclass” of the given three elements. Consequently, bunch[1] has the type B and not B1. That is why bunch[1].t1 does not compile.
Of course you can print the actual type of an array element at runtime with type(of:), or check it against B1. The latter is better done with optional binding:
if let b1 = bunch[1] as? B1 {
print(b1.t1)
}
The simple answer is that once you declare the array of type B then the compiler treats all the elements in the array to be of type B and will not automatically infer methods based on any subclasses present. It seems you are wondering why the compiler/array is not smart enough to determine the class of a let declared array, when in theory it could be. I'm not sure many languages support such a feature, in any event you can do the same thing by simply unwrapping it like you did, since YOU know it in this case.
Related
I'm trying to compare two objects whose type is known at run time but not at compile time. So far I have the following function which seems to work as written:
/// Compares two objects of the given type.
/// Returns -1 if a is "less than" b, 1 if a is "greater than" b, 0 if they are equal, and nil if no comparison could be made.
func compare<T: Comparable>(type: T.Type, a: Any?, b: Any?) -> Int? {
guard let at = a as? T, let bt = b as? T else { return nil }
return at < bt ? -1 : at > bt ? 1 : 0
}
The problem is, the type is not necessarily known to comply with the Comparable protocol at compile time; I really need to be able to pass in any type (Any.Type), not just Comparable ones. I'd then like the function to return nil if the passed-in type does not conform to Comparable. How can I do this?
Edit (30/08/2018): Some more context. I'm using this function to sort various arrays of strings, integers, and other Comparable types. However, because these arrays are retrieved via reflection the element types are not known at compile time. I know that they will always be Comparable but the compiler doesn't. To resolve this, I'm passing in the type as a separate parameter as shown. However, because I'd like to keep the logic as general as possible I'm performing this sort function inside a conditional statement which chooses the necessary type from an array. The type of this array must be [Any.Type] to hold the required types, even though all its contents conform to Comparable (String.Type, Date.Type, etc.).
I want to pass an array of types to a function - but I'm not sure what is the type of a Swift Type.
Let's say I have two classes:
MyClass and AnotherClass.
I want an array like this [MyClass, AnotherClass].
What will be the type of the array?
It would be of AnyClass type which is base for all object types.
func checkClassTypes(types: [AnyClass]) {
types.forEach { type in
// Your custom classes don't necessarily have to subclass from NSObject.
print(type is NSObject.Type)
}
}
checkClassTypes([MyClass.self, AnotherClass.self])
If you need to contain only class types, AnyClass will works:
class MyClass {
//...
}
class AnotherClass {
//...
}
let classTypes: [AnyClass] = [MyClass.self, AnotherClass.self]
If you want to include some value types, you may need to use Any or Any.Type:
let types: [Any] = [Int.self, String.self, MyClass.self, AnotherClass.self]
let anyTypes: [Any.Type] = [Int.self, String.self, MyClass.self, AnotherClass.self]
But all three types, AnyClass, Any or Any.Type, are hard to use. You will soon find it is very hard just to instantiate a class in the array.
I'm not sure I understand your question about Swift Types correctly, but Swift doesn't specify a Type as being a certain kind of variable. Swift defines that variables in code can have a certain type, which can be a float, a string, integer, etc. When it is said that Swift is type safe and infers a type, this means that Swift requires you to be clear on the type of values that you store in your variables (type safety) and that Swift will try to infer what type a variable is when you specify a value (type inference).
The type of your variable can also be more complex, like an enum, a struct or a class. Swift even sees an array or other collections as types. In this sense a Type could be defined as a piece of memory in which a constant or a variable (or a collection of these) is stored. A type can be different things. The great thing about having a Type as a constant/variable in which I store information, is that I can now defer the type of a variable until runtime, rather than that I have to specify it in my code. Or I can specify a structure of a generic type and later I can define different kinds of variables with the same structure, but of different types. Please, read for a more detailed understanding of dealing with types the Swift programming language from Apple. It is free and can be downloaded in the iBooks Store.
Now coming back to your second question of an array of [MyClass, AnotherClass] is kind of like deferring the types of your array until later. You can initially define your array as an array of AnyObjects or more swift-like AnyClass, like
myArray = [AnyClass]()
Later you can put different types of variables/objects in that array, like a Double, a String, or an object with a specific class.
You should be careful about building such an array, however. This array is not type safe and you haven't specified the type of object at a certain index. To prevent xcode having trouble with your code and your program from crashing, you should always type cast the individual objects in your array. For instance like
if let myObject = myArray[2] as? myClass { do something }
If you don't typecast your array both xcode and your program will do weird things.
I hope this answered your question.
Kind regards,
MacUserT
I'm guessing that you really want to create an array of instances of the two classes rather than an array of the class definitions. If so, you will be creating an array of Any or AnyObject, not AnyClass.
class MyClass {
}
class OtherClass {
}
let m = MyClass()
let o = OtherClass()
let array1 = [m, o] as [Any]
// or
let array2 = [m, o] as [AnyObject]
What is the difference between an any type and generic type in swift?
Any Type Example:
let swiftInt: Int = 1
let swiftString: String = "miao"
var array: [Any] = []
array.append(swiftInt)
array.append(swiftString)
Generic Type Example:
func duplicate<T>(item: T, numberOfTimes n: Int) -> [T] {
var buffer : [T] = []
for _ in 0 ..< n {
buffer.append(item)
}
return buffer
}
Is this a matter of preference because both appear to solve the same problem by being able to substitute the desired type.
I'm not going to explain generics in details and i'll just point out the essential differences.
In the first example, you'll be able to append any type in that array, without being able to restrict beforehand your array to a specific type and to leverage compile time checks to guarantee that the array will not contain extraneous types. Not much to see in that example.
The second example contains instead a generic function that provides all of the above functionalities, consistency checks on the content of the array will come for free and if you want you'll also be able to specify additional characteristics of that generic type T, like requesting that it implements a specific protocol (e.g. limit duplicate() to object that implement Comparable or Equatable).
But that is just a simple example of a generic function, you can also have parametrized classes (what you'll use the most) and there are a lot of additional functionalities.
Never use Any as a poor-man generics, real generics are way more flexible, add useful checks and make more explicit your intentions, with minimal additional effort required to implement them.
Any means "I don't want any type checking and I won't be able to call type-specific methods without casting"
For example, try to call:
var array: [Any] = [1, 2]
var sum = array[0] + array[1] // you cannot do this! you have to cast to Int first
A generic type is a placeholder for a type. When used, a concrete type is used instead of it (e.g. an Int or a String).
In short, never use Any. There are very very few specific situations when Any is what you want to use.
import Foundation
class A: NSObject {
}
class B: A {
}
let array = [B(),B(),B(),B()]
array as? [A] //this does not works
B() as? A // this works
as? is a conditional downcasting operation (which can fail, and therefore returns an optional) – and you're trying to upcast (which can never fail, and therefore you can do freely).
You therefore can just use as in order to freely upcast the array – or rely on type inference, as Casey notes.
let arrayOfB = [B(),B(),B(),B()]
let arrayOfA = arrayOfB as [A] // cast of [B] to [A] via 'as'
let explicitArrayOfA : [A] = arrayOfB // cast of [B] to [A] via inference of explicit type
func somethingThatExpectsA(a:[A]) {...}
somethingThatExpectsA(arrayOfB) // implicit conversion of [B] to [A]
Although the compiler's error of A is not a subtype of B is certainly unhelpful in the context. A more suitable error (or maybe just a warning) could be 'as?' will always succeed in casting '[B]' to '[A]' – did you mean to use 'as'?.
You should also note that for your example:
B() as? A // this works
You'll get a compiler warning saying Conditional cast from 'B' to 'A' always succeeds. Although why you get a compiler warning, rather than error for this – I can't say. I suspect it's due to the special way in which array casting happens.
Because B is an A, you can drop the conditional cast and
let thing = array as [A]
Or declare the destination as the desired type and forget about casting
let thing : [A] = array
This works:
func removeObject<T : Equatable>(object: T, array: [T]) -> Array<T>
{
return array.filter() { $0 != object }
}
let threeThings = ["one", "two", "three"]
twoThings = removeObject("three", threeThings)
However, I'd like to check for inequality with this !==. Then I get error "Type 'T' does not conform to protocol 'AnyObject'"
How can this code be fixed? (I've see the code here but I'd like to learn how to use filter properly).
The identical operator === and its negation !== are only defined for
instances of classes, i.e. instances of AnyObject:
func removeObject<T : AnyObject>(object: T, array: [T]) -> Array<T>
{
return array.filter() { $0 !== object }
}
=== checks if two variables refer to the same single instance.
Note that your code
let threeThings = ["one", "two", "three"]
twoThings = removeObject("three", threeThings)
does still compile and run, but gives the (perhaps unexpected) result
[one, two, three]
The Swift strings (which are value types and not class types) are automatically
bridged to NSString, and the two instances of NSString representing "three"
need not be the same.
If you want to use !== instead of !=, then, instead of the type constraint <T : Equatable> say <T : AnyObject>. All you have to do is listen to what the error message is telling you!
Note that this has nothing to do with using the filter function. It is simply a matter of types. You cannot use a method with an object of a type for which that method is not implemented. !== is implemented for AnyObject so if you want to use it you must guarantee to the compiler that this type will be an AnyObject. That is what the type constraint does.
!== checks for "identity", not "equality". Identity is a property of reference types which all support the AnyObject protocol, and it means that the two variables that you are comparing point to the same actual object, and not just another object with the same value.
That means you can't use === or !== with normal value types, like strings, integers, arrays, etc.
Try typing this into a playground:
let a = "yes"
let b = a
println(a === b)
You should get a message saying that String doesn't conform to the AnyObject protocol, because String is a value type (a struct) and doesn't support AnyObject, so you can't use === with it.
You can use === with any instance of a class, because classes are reference types, not value types.
You could put a constraint on T requiring that it conform to AnyObject.
func removeObject<T : Equatable where T: AnyObject>...
It should work, then, for reference types (including all class instances). But then your function won't work for value types (which include all structs).
Why do you need to check for identity (===) instead of equality (==)?