13

I have a Swift function that accepts Any and I want it to be able to accept an array of Strings, an array of Ints, a mixed array, or an array of arrays, etc. It also can accept just a String or an Int, etc, not in an array.

So I have this:

    private func parse(parameter: Any) {
        if parameter is Int {
            // Int
        } else if (parameter is Float) || (parameter is Double) {
            // Double
        } else if parameter is String {
            // String
        } else if parameter is Bool {
            // Bool
        } else if let array = parameter as? [Any] {
            // Should catch all Arrays
        } else {
            assert(false, "Unsupported type") // [String] ends up here
        }
    }

But if I call parse(["Strings"]), the assert is raised. How can I catch all types of Arrays?

edit - there was some confusion as to what I'm trying to accomplish. I basically need to return a String based on the type, so Int -> "" and String -> "", so an array would make recursive calls to return "..."

This post is marked as a duplicate, but that other question is about Javascript, not Swift.

1
  • user3352495 false, a check against [Any] still does not works as expected (Swift 5) Commented Jan 3, 2021 at 16:58

4 Answers 4

13

I finally found the way to do that, which is to use NSArray for casting.

private func parse(x: Any) {
    if let o = x as? [Any] {
        println("[Any]")
    }
    if let o = x as? [AnyObject] {
        println("[AnyObject]")
    }
    if let o = x as? NSArray {
        println("NSArray")
    }
}

let a: [Any] = ["bar"]
let b: [AnyObject] = ["bar"]
let c = ["foo", 3.14]

parse(a) // ==> [Any]
parse(b) // ==> [AnyObject], and also NSArray
parse(c) // ==> NSArray

It look so that an array containing values of Any internally represented in NSArray. (But should it be able to cast c to [Any]...? I'm suspecting it's a bug.)

Sign up to request clarification or add additional context in comments.

3 Comments

Gimme a minute to try this idea in my implementation!
Bam! That works! I'm going to keep trying to simplify to get a good example to radar, but I'll use this in the mean time.
Swift 5 it looks like it doesn't work anymore.. "error: cannot convert value of type 'String' to expected element type 'AnyObject'" for line let b: [AnyObject] = ["bar"]
7

The key to understanding typing and type related issues in Swift is that all roads lead to protocols.

The challenge of this problem is detecting any type of array, not just one concrete type. The OP's example failed because [Any] is not a base class or a generalized pattern of [String], that is to say, that (from what I can tell), in Swift [T] is not covariant on T. Beyond that, you cannot check for SequenceType or CollectionType since they have associated types (Generator.Element).

The idiomatic solution is thus to use a marker protocol to indicate which types you want to match your criteria. As illustrated below, you achieve this by creating an empty protocol, and associating it with the types of interest.

import Foundation


protocol NestedType {}
extension Array: NestedType {}
extension Set: NestedType {}
extension Dictionary: NestedType {}
extension NSSet: NestedType {}

protocol AnyTypeOfArray {}
extension Array: AnyTypeOfArray {}
extension NSArray: AnyTypeOfArray {}

protocol AnyTypeOfDictionary {}
extension Dictionary: AnyTypeOfDictionary {}


func printType(v:Any) {
    if v is NestedType {
        print("Detected a nested type")
    }

    if v is AnyTypeOfArray {
        print("\t which is an array")
    }

    else if v is AnyTypeOfDictionary {
        print("\t which is a dictionary")
    }
}


printType([String:Int]())
printType([Int]())
printType(NSArray())

The output of which is:

Detected a nested type
     which is a dictionary
Detected a nested type
     which is an array
Detected a nested type
     which is an array

1 Comment

This is the correct Answer. And the most "swifty" 🔶
3

One way you can do this is to separate the function out to two separate implementations (with the same name), one that takes anArray and one for everything else. You'll also need to make them generic functions instead of using the Any type. With that setup, Swift can use type inference to figure out the best function to call.

I'd implement it something like this (I'm just printlning the type to show where things end up):

func parse<T>(parameter: T) {
    if parameter is Int {
        println("Int")
    } else if (parameter is Float) || (parameter is Double) {
        println("Double")
    } else if parameter is String {
        println("String")
    } else if parameter is Bool {
        println("Bool")
    } else {
        assert(false, "Unsupported type")
    }
}

func parse<T>(parameter: Array<T>) {
    println("Array")
    for element in parameter {
        // Recursively parsing...
        parse(element)
    }
}

Then calling it like this:

parse(1)  // Int
parse(0.1) // Double
parse("asdf") // String
parse(true) // Bool
parse(["asdf", "asdf"]) // Array -> String String

Outputs:

Int
Double
String
Bool
Array
String
String

5 Comments

Running your code as is works perfectly. Running it in the context of my code, however, breaks it. I'm unpacking the parameters from an array of type [Any] and calling parse() on each element. No matter what the element is, the first function is called, never the second.
This fails: parseAny([["a", "b", "c"]]) func parseAny(values: [Any]) { for value in values { parse(value) } }
And so does the same, except changing parseAny([Any]) to parseAny<T>(Array<T>)
Hmm, yep, you're right... offhand I'm not sure why, but I'll think about it a bit and see if I can come up with something.
The only other thing I can think of is some sort of wrapper struct.
0

You can use the _stdlib_getTypeName that returns the mangled type name for the given value.

For example:

    var myString = "String"
    var myInteger = 10
    var myArray = [10,22]
    var myDictionary = ["one": 1, "two": 2, "three": 3]
    println("\(_stdlib_getTypeName(myString))")
    println("\(_stdlib_getTypeName(myInteger))")
    println("\(_stdlib_getTypeName(myArray))")
    println("\(_stdlib_getTypeName(myDictionary))")

The result will be:

_TtSS // for String
_TtSi // for integer
_TtSa // for array
_TtVSs10Dictionary // for dictionary

1 Comment

This would technically let me know when I've been given array, but wouldn't let me treat it as an array without trying to cast it. The problem is, a forced downcast using parameter as [Any] causes a runtime crash. Also, this is super hacky.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.