Reputation: 13
This is the case: Suppose that I get a variable as an AnyObject or Any. Then, I must to cast the variable to know if it's an array with objects of a specific type.
func myFuction(receivedObject: AnyObject) {
if let validDogs = receivedObject as? [Dog] {
print("Received object is an array of dogs")
// Do something with valid dogs
}
if let validCats = receivedObject as? [Cat] {
print("Received object is an array of cats")
// Do something with valid cats
}
}
This code works if the received object is not an empty array (not nil), but fails if the received object is an empty array because my log prints this two messages:
"Received object is an array of dogs"
"Received object is an array of cats"
Which suggest that for an empty array the cast fails. So, is there a way to fix that?
Upvotes: 0
Views: 329
Reputation: 299605
Code like this strongly suggests a deep problem in your type design, and should be resolved by getting rid of the AnyObject
. It is very rare that passing AnyObject
is the correct tool.
You've said something very important here:
This code works if the received object is not an empty array (not nil)
An empty array is not the same thing as nil
. If you pass nil
, that's an Optional. Optional<[Cat]>
is not the same thing as [Cat]
and you should not expect it to consistently as?
cast, particularly if it's nil
. If this came from ObjC, that nil
is bridged to an actual Obj-C nil
(which is just the value 0), and the runtime has literally nothing to work with.
You said that you receive both log lines, though. That suggests both as?
casts are succeeding, not failing. If that's the case, I assume this is an NSArray
, and an empty NSArray
can legitimately be as?
cast to an array of anything (NSArray
has no element type internally). So the above is expected.
If it is really is optional, then to the question of "how do I determine that it's an optional and then unwrap it and then work out if it's [Cat]
," the answer is "stop; you've gone too far with AnyObject
." Start by redesigning this so you don't need that, which generally means figuring out your types earlier.
The correct way to do what you're trying to do is generally with overloads, not as?
casting:
func myFuction(receivedObject: [Dog]) {
print("Received object is an array of dogs")
// Do something with valid dogs
}
func myFuction(receivedObject: [Cat]) {
print("Received object is an array of cats")
// Do something with valid cats
}
You cannot just pass AnyObject
to the above functions. You need to know the types of the things you're working with. (Your comment that you're stripping the types with as! AnyObject
suggests that you do know the types already, and are actively throwing them away and trying to later recover them. If this is the case, the above code is exactly the right thing. Don't throw away the types.)
While there are some corner cases where you cannot know those types (because you literally are accepting "any object at all"), in the vast majority of cases failure to know your types suggests a design problem.
Upvotes: 4
Reputation: 22507
@RobNapier's answer is correct from a good programming point of view - I see no reason whatever to be doing what you are attempting, but it shows up something interesting.
Firstly, you cannot call your function with an array of anything - arrays, as struct
s, do not conform to AnyObject
(classes do). However, you call the function by a forced cast - as anything can be cast to AnyObject (presumably by wrapping the struct in a degenerate class), the call compiles.
Secondly, your question has the inference the wrong way round - you say "Which suggest that for an empty array the cast fails." - not so, the cast succeeds in both cases...
Let's see what actually gets passed in:
class Pet { var name: String { return "" } }
class Dog: Pet { override var name: String { return "Fido" } }
class Cat: Pet { override var name: String { return "Cfor" } }
func myFuction(receivedObject: AnyObject) {
print("myFuction called with \(receivedObject)") // ***
if let validDogs = receivedObject as? [Dog] {
print("Received object is an array of dogs")
// Do something with valid dogs
}
if let validCats = receivedObject as? [Cat] {
print("Received object is an array of cats")
// Do something with valid cats
}
}
var a: [Cat] = [Cat()]
//myFuction(receivedObject: a) // Argument type '[Cat]' does not conform to expected type 'AnyObject'
myFuction(receivedObject: a as! AnyObject) // Forced cast from '[Cat]' to 'AnyObject' always succeeds; did you mean to use 'as'?
a = []
myFuction(receivedObject: a as! AnyObject) // Forced cast from '[Cat]' to 'AnyObject' always succeeds; did you mean to use 'as'?
Output:
myFuction called with (
"__lldb_expr_97.Cat"
)
Received object is an array of cats
myFuction called with (
)
Received object is an array of dogs
Received object is an array of cats
So, when it's called with a non-empty array, the type is inferred from the members and the cast only succeeds if the members are of the right type. An empty array has no members, and thus could equally well be [Cat]
or [Dog]
or indeed [String]
.
Try
let b = [Cat(), Dog()]
myFuction(receivedObject: b as! AnyObject)
This prints
myFuction called with (
"__lldb_expr_107.Cat",
"__lldb_expr_107.Dog"
)
(and does not print a cast "success")
The long and the short of it is
A) the force as! AnyObject
effectively throws away the array type and passes something resembling a tuple of elements which your let _ = as?
is able to reassemble into an array from the types of the elements.
B) re-read @RobNapier's answer - that's the way to go!
Upvotes: 3