fragilecat fragilecat - 1 month ago 8
Swift Question

Are there any concrete examples when using the Any type is a good solution?

We all know that swift has a strong type system, and as such I lean towards using this system to my advantage :)

Here's what Apple has to say about using the Any type:


Type Casting for Any and AnyObject

Swift provides two special type aliases for working with non-specific
types:

AnyObject can represent an instance of any class type. Any can
represent an instance of any type at all, including function types.
NOTE

Use Any and AnyObject only when you explicitly need the behavior and
capabilities they provide. It is always better to be specific about
the types you expect to work with in your code.


I don't know if it's me, but I kind of think the code starts to smell when using these (This maybe my lack of experience using them) - I know AnyObject is required/useful for interacting with Objective-C so I get that there is a practical nature to AnyObject's use.

So I wanted to know what concrete examples represent a good use of the Any type.

For example is it could be used to pass unknown content to a POST request constructor method where the method could safely use optional chaining to examine the unknown content.

Apple's example from the Swift Programming Language (Swift 2.2)


Any

Here’s an example of using Any to work with a mix of different types,
including function types and non-class types. The example creates an
array called things, which can store values of type Any:


var things = [Any]()

things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
things.append({ (name: String) -> String in "Hello, \(name)" })

Answer

When should I use [Any]?

Any is an abstract type that all other types implicitly conform to. Therefore because it could be anything – it must be guaranteed to do nothing.

For that very reason, typing a variable as Any is nonsensical. If I define this:

let itCouldBeAnything : Any = "Actually it's a string"

It now can't do anything (without type casting):

enter image description here

You should always use the most descriptive type available when defining something, and because of Any's abstract nature, there will always be a more descriptive type available – in this case, String should've been used.

Now let’s think about arrays. As you say, Apple demonstrates that you can use an array of Any to mix together different types.

var things = [Any]()

things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))

Great. But what does this array actually represent? It’s just a collection of junk – the programming equivalent of the backseat of my car.

If we try and do something with one of the elements...

enter image description here

We can’t. It could be anything – so we can’t do anything with it. Sure, we can type cast the elements with a switch or if statement to handle the different possibilities – but all we’ll succeed in doing there is splitting our array back into distinct types. So what was the point in using [Any] to begin with?

An array should always represent a collection of related data. There’s no point of an array if the elements share nothing in common. If the different types that the array can store have common functionality, then you should define a protocol with this common funtionality:

protocol ACommonProtocol {
    // define common functionality
}

We can then conform the types that the array can store to it:

extension Int : ACommonProtocol {}
extension Double : ACommonProtocol {}
extension String : ACommonProtocol {}
extension Movie : ACommonProtocol {}

Now our array can be of type [ACommonProtocol]. This is a significant improvement from Any, we’ve narrowed the concrete types that the elements could be down to 4 – and we can now use any common functionality that we define in the protocol on the elements without type-casting. Our elements also are now explicitly related, which actually gives the array some meaning (assuming the protocol represents a meaningful concept).

If the different types that the array can store don't have any common functionality (but are still related in some meaningful way) – then another way of expressing this relationship is through using an enum with associated values:

// obviously this enum (and possibly its cases) should have a far better names
enum IntDoubleStringOrMovie { 
    case int(Int)
    case double(Double)
    case string(String)
    case movie(Movie)
}

Now our array can be of type [IntDoubleStringOrMovie] – which has the added benefit of allowing us to use an exhastive switch in order to determine the element type:

for element in array {
    switch element {
    case let .int(int):
        print(int)
    case let .double(double):
        print(double)
    case let .string(string):
        print(string)
    case let .movie(movie):
        print(movie)
    }
}

So for this reason, you should never ever have to use [Any] in Swift. If you ever find yourself in a situation of using [Any], you should be re-considering your data structure. Whether that involves splitting the array up into sub-arrays, conforming the elements to a common protocol (if they have common functionality), using an enum with associated values (if they have no common functionality) or using a different collection type altogether, is entirely up to you.

Although, that all being said, there's an edge case that I've missed out on – namely functions and tuples. You cannot conform functions or tuples to a custom protocol – so the above solution won't work with them.

However, if you find yourself mixing together tuples or functions of different types in an array – you should certainly be re-thinking your logic. Instead of tuples, use structs or enums with associated values instead. For functions, you should most likely be using generics and/or protocols with associated types in a struct or class instead.

But what about interfacing with Objective-C?

Any is sometimes necessary for bridging with Objective-C’s inferior type system – and therefore you may well find yourself using [Any] with Objective-C APIs, which is acceptable (although with the bridging of Objective-C's lightweight generics to Swift, it should be uncommon).

The only thing I would say about this is once you’re back in your Swift logic, you should always convert an [Any] back to a meaningful array type through type casting, or unpacking it into a more appropriate data structure. You shouldn’t sacrifice type safety in Swift just because Objective-C lacks in that regard.

But what about [AnyObject]?

AnyObject is pretty much as vague as Any in pure Swift. The only thing it guarantees is that it’s a class. However, as of Swift 3, anything can be bridged to Objective-C by being boxed in a _SwiftValue. For this reason, [AnyObject] is just as (if not more) questionable as [Any].