Stuart
Stuart

Reputation: 37043

Cast [AnyObject] to generic type that may or may not be a collection type

I have an array of AnyObject, and I would like to cast it (or one of its objects) to a generic type T which may or may not be an array.

My first attempt:

class MyClass<T> {

    func someMethod() -> T? {

        let anyObjectArray: [AnyObject] = // ... array obtained from Objective-C framework

        if let objectsAsCollection = anyObjectArray as? T {  // Cast always fails
            return objectsAsCollection
        } else if let firstObject = anyObjectArray.first as? T {
            return firstObject
        } else {
            return nil
        }
    }
}

The first if let cast fails when T is an Array (e.g. T.self == Array<SomeObject>.self).

I would try something like this, but it involves introducing another generic parameter somehow, so isn’t valid as written:

extension MyClass where T == Array<U> {

    fun someMethod() -> T? {

        let anyObjectArray: [AnyObject] = // ... array obtained from Objective-C framework

        if let objectsAsCollection = anyObjectArray.flatMap({ $0 as? U }) {
            return objectsAsCollection
        } else if let firstObject = anyObjectArray.first as? T {
            return firstObject
        } else {
            return nil
        }
    }
}

The reason for wanting to do this is that I am using an Objective-C framework (RestKit) to get results from a REST API. I am building a layer on top in Swift which passes these results back as a specific type depending on the request that was made, so I have a Result type that looks something like this:

enum Result<T>: {
    case Success(T)
    case Failure(Error)
}

Sometimes T is a single object, sometimes it is an arrays of objects, but the Objective-C framework always returns results as an array.

Upvotes: 1

Views: 1052

Answers (1)

Sulthan
Sulthan

Reputation: 130092

The following syntax will work:

class MyClass<T> {
    func someMethod() -> T? {
        // let's not convert to [AnyObject] here, keep it as NSArray
        let anyObjectArray: NSArray = ...

        // For some reason conversion from [AnyObject] to [T] fails...
        // However, conversion from NSArray is special
        if let objectsAsCollection = anyObjectArray as? T {
            return objectsAsCollection
        } else if let firstObject = (anyObjectArray as [AnyObject]).first as? T {
            return firstObject
        } else {
            return nil
        }
    }
}

However, it is just a workaround for a probable bug. We can boil down the problem to the following:

class GenericClass<T> {
    func someMethod() {
        let anyObjectArray: [AnyObject] = ["test1", "test2", "test3"]

        print(T) //Array<String>
        print(T.self == Array<String>.self) //true

        print(anyObjectArray is [String]) //true
        print(anyObjectArray is T) //false - BUG
    }
}

let instance = GenericClass<[String]>()
instance.someMethod()

(reported: SR-1054)

which looks like a clear bug to me. It actually looks like another instance of the bug discussed in Type of optionals cannot be inferred correctly in swift 2.2

Upvotes: 3

Related Questions