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
Related
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.
Overview
I would like to use switch statement by matching array type. I have the following classes.
Class:
class A {}
class B : A {}
Switch on single value works:
let a : A = B()
switch a {
case let b as B:
print("b = \(b)")
default:
print("unknown type")
}
Switch on array (Compilation Error):
let aArray : [A] = [B(), B()]
switch aArray {
case let bArray as [B] :
print("bArray = \(bArray)")
default:
print("unknown type")
}
Error:
Downcast pattern value of type '[B]' cannot be used
Note: Tested on Swift 4
Question:
How can I achieve this ?
In Swift 4.1, you get a better error message (thanks to #11441):
Collection downcast in cast pattern is not implemented; use an explicit downcast to '[B]' instead
In short, you're hitting a bit of the compiler that isn't fully implemented yet, the progress of which is tracked by the bug SR-5671.
You can however workaround this limitation by coercing to Any before performing the cast:
class A {}
class B : A {}
let aArray : [A] = [B(), B()]
switch aArray /* or you could say 'as Any' here depending on the other cases */ {
case let (bArray as [B]) as Any:
print("bArray = \(bArray)")
default:
print("unknown type")
}
// bArray = [B, B]
Why does this work? Well first, a bit of background. Arrays, dictionaries and sets are treated specially by Swift's casting mechanism – despite being generic types (which are invariant by default), Swift allows you to cast between collections of different element types (see this Q&A for more info).
The functions that implement these conversions reside in the standard library (for example, Array's implementation is here). At compile time, Swift will try to identify collection downcasts (e.g [A] to [B] in your example) so it can directly call the aforementioned conversion functions, and avoid having to do a full dynamic cast through the Swift runtime.
However the problem is that this specialised logic isn't implemented for collection downcasting patterns (such as in your example), so the compiler emits an error. By first coercing to Any, we force Swift to perform a fully dynamic cast, which dispatches through the runtime, which will eventually wind up calling the aforementioned conversion functions.
Although why the compiler can't temporarily treat such casts as fully dynamic casts until the necessary specialised logic is in place, I'm not too sure.
As you may know, Swift can infer types from usage. For example, you can have overloaded methods that differ only in return type and freely use them as long as compiler is able to infer type. For example, with help of additional explicitly typed variable that will hold return value of such method.
I've found some funny moments. Imagine this class:
class MyClass {
enum MyError: Error {
case notImplemented
case someException
}
func fun1() throws -> Any {
throw MyError.notImplemented
}
func fun1() -> Int {
return 1
}
func fun2() throws -> Any {
throw MyError.notImplemented
}
func fun2() throws -> Int {
if false {
throw MyError.someException
} else {
return 2
}
}
}
Of course, it will work like:
let myClass = MyClass()
// let resul1 = myClass.fun1() // error: ambiguous use of 'fun1()'
let result1: Int = myClass.fun1() // OK
But next you can write something like:
// print(myClass.fun1()) // error: call can throw but is not marked with 'try'
// BUT
print(try? myClass.fun1()) // warning: no calls to throwing functions occur within 'try' expression
so it looks like mutual exclusive statements. Compiler tries to choose right function; with first call it tries to coerce cast from Int to Any, but what it's trying to do with second one?
Moreover, code like
if let result2 = try? myClass.fun2() { // No warnings
print(result2)
}
will have no warning, so one may assume that compiler is able to choose right overload here (maybe based on fact, that one of the overloads actually returns nothing and only throws).
Am I right with my last assumption? Are warnings for fun1() logical? Do we have some tricks to fool compiler or to help it with type inference?
Obviously you should never, ever write code like this. It's has way too many ways it can bite you, and as you see, it is. But let's see why.
First, try is just a decoration in Swift. It's not for the compiler. It's for you. The compiler works out all the types, and then determines whether a try was necessary. It doesn't use try to figure out the types. You can see this in practice here:
class X {
func x() throws -> X {
return self
}
}
let y = try X().x().x()
You only need try one time, even though there are multiple throwing calls in the chain. Imagine how this would work if you'd created overloads on x() based on throws vs non-throws. The answer is "it doesn't matter" because the compiler doesn't care about the try.
Next there's the issue of type inference vs type coercion. This is type inference:
let resul1 = myClass.fun1() // error: ambiguous use of 'fun1()'
Swift will never infer an ambiguous type. This could be Any or it could beInt`, so it gives up.
This is not type inference (the type is known):
let result1: Int = myClass.fun1() // OK
This also has a known, unambiguous type (note no ?):
let x : Any = try myClass.fun1()
But this requires type coercion (much like your print example)
let x : Any = try? myClass.fun1() // Expression implicitly coerced from `Int?` to `Any`
// No calls to throwing function occur within 'try' expression
Why does this call the Int version? try? return an Optional (which is an Any). So Swift has the option here of an expression that returns Int? and coercing that to Any or Any? and coercing that to Any. Swift pretty much always prefers real types to Any (and it properly hates Any?). This is one of the many reasons to avoid Any in your code. It interacts with Optional in bizarre ways. It's arguable that this should be an error instead, but Any is such a squirrelly type that it's very hard to nail down all its corner cases.
So how does this apply to print? The parameter of print is Any, so this is like the let x: Any =... example rather than like the let x =... example.
A few automatic coercions to keep in mind when thinking about these things:
Every T can be trivially coerced to T?
Every T can be explicitly coerced to Any
Every T? can also be explicitly coerce to Any
Any can be trivially coerced to Any? (also Any??, Any???, and Any????, etc)
Any? (Any??, Any???, etc) can be explicitly coerced to Any
Every non-throwing function can be trivially coerced to a throwing version
So overloading purely on "throws" is dangerous
So mixing throws/non-throws conversions with Any/Any? conversions, and throwing try? into the mix (which promotes everything into an optional), you've created a perfect storm of confusion.
Obviously you should never, ever write code like this.
The Swift compiler always tries to call the most specific overloaded function is there are several overloaded implementations.
The behaviour shown in your question is expected, since any type in Swift can be represented as Any, so even if you type annotate the result value as Any, like let result2: Any = try? myClass.fun1(), the compiler will actually call the implementation of fun1 returning an Int and then cast the return value to Any, since that is the more specific overloaded implementation of fun1.
You can get the compiler to call the version returning Any by casting the return value to Any rather than type annotating it.
let result2 = try? myClass.fun1() as Any //nil, since the function throws an error
This behaviour can be even better observed if you add another overloaded version of fun1 to your class, such as
func fun1() throws -> String {
return ""
}
With fun1 having 3 overloaded versions, the outputs will be the following:
let result1: Int = myClass.fun1() // 1
print(try? myClass.fun1()) //error: ambiguous use of 'fun1()'
let result2: Any = try? myClass.fun1() //error: ambiguous use of 'fun1()'
let stringResult2: String? = try? myClass.fun1() // ""
As you can see, in this example, the compiler simply cannot decide which overloaded version of fun1 to use even if you add the Any type annotation, since the versions returning Int and String are both more specialized versions than the version returning Any, so the version returning Any won't be called, but since both specialized versions would be correct, the compiler cannot decide which one to call.
Lets say I have these:
class Animal {
}
class Cat: Animal {
var meow = "meow"
}
Now I have a variable:
var catMaybe: Cat? = /* ... */
and at some point later I do this:
var obj1 = catMaybe as Animal? // ①
and it compiles fine but I could have wrote this other code too:
var obj2 = catMaybe as? Animal // ②
only this time I get a warning. Which confuses me since I thought both were doing the same.
warning: cast from 'Cat?' to unrelated type 'Animal' always fails
var obj4 = catMaybe as? Animal
~~~~~~~~ ^ ~~~~~~
What is the real difference between ① and ②?
Edit1
I am interested in what is really happening in the background. I would go and read the assembly code to find why ② always fails but unfortunately I am not very good at it :(
In ②, since Animal is not an optional, I thought the compiler would know that catMaybe must be unwrapped first and if the operation succeed try to cast it to Animal. In any case of error it would return nil (since I am using as?) Why the compiler does not apply this logic? What stops him?
Edit2
I am using swift 1.2
With as? you cast an optional,let say you do a if letif let object = catMaby as? Animal{
/do some code with object
}
You are not sure if catMaby has a value.as? are for if let
Explanation
The optional form, as?, returns an optional value of the type you are trying to downcast to. The forced form, as, attempts the downcast and force-unwraps the result as a single compound action.So if you are not sure that catMaby is a Animal,put as?.There in the code checked if catMaby is Animal and checked if catMaby is an optional
So as? is for optional values,as! is the same but gives an error if you try to unwrap it
You get the warning since catMaybe is an optional. Optionals are of an enum type and only after you unwrap catMaybe (with either ? or !) you will get the correct object (of type Cat).
Since catMaybe is an enum (which does not inherit Animal) you get that warning.
So this should work :
var obj2 = catMaybe! as? Animal // upcast the real unwrapped value
The first version works since you keep catMaybe as an optional (an optional to an Animal object when unwrapped)
This is made complicated by mixing together two things – optionals, and casts. It might be clearer to ditch the optionals, look at that, and then reintroduce the optionals. Note this is all Xcode 6.3/Swift 1.2.
Here’s the simplest case:
class Animal { }
class Cat: Animal { }
let c = Cat()
let a = c as Animal // fine
The compiler knows that every Cat reference can be guaranteed convertible to Animal, so it lets you use as without complaint. If you do it the other way around though:
let a: Animal = Cat()
let c = a as Cat // compiler error
The compiler complains that as cannot be used – an Animal might not be a Cat. You must use as?, which returns an optional value – either a successfully converted Cat, or nil:
let a: Animal = Cat()
if let c = a as? Cat // c will be a Cat?
Swift also lets you use as? when the conversion is guaranteed to work:
let c = Cat()
let a = c as? Animal // a will be an Animal?
though it sometimes give you a warning that you’re using a conditional cast on something that will always succeed. But its only a warning.
That’s pretty much it for as vs as?. as for when the compiler can know for certain the cast will work, as? that returns an optional for when it might not. This doesn’t just apply to super/subclasses, it also applies to other conversions guaranteed to work, such as Swift.Array to NSArray, Sometype to Sometype?.
OK now to add in the other optional. Suppose instead you have:
let c: Cat? = Cat()
let a = c as Animal? // this is fine
So an optional Cat will convert to an optional Animal using as. This is because optionals have support for covariance – Swift looks at the contents of the optional being converted from, and the contents of the optional being converted to, and if the conversion works on the contents, it will allow the conversion.
By the way, this also works with arrays:
let c: [Cat] = [Cat()]
let a = c as [Animal]
Since Cat as Animal is allowed, so is [Cat] as [Animal].
However, casting away the optional is not allowed:
let c: Cat? = Cat()
let a = c as Animal
because you cannot discard optionality using as, just like this isn’t allowed:
let i: Int? = 1
let j = i as Int
And replacing as with as? here doesn’t help – you still can’t do the cast, so while it might compile, the result will always be nil (the compiler may warn you about this).
Finally, you can also do conditional casting inside the optional:
let c: Animal? = Cat()
let a = c as? Cat?
but beware! Now a will be of type Animal??, because the optional now contains an optional. So if you ever have to do this, you’re better off doing like so:
let c: Animal? = Cat()
let a = c.flatMap { $0 as? Cat? }
Or, you would be if this didn’t crash the compiler :-(. But the code ought to be correct.
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 (==)?