获取Swift中的数组元素类型(通过反射)

Get a type of Element of an array in Swift (through reflection)

假设我有以下代码

class Foo {
}

var fooArray : Array<Foo> = Array<Foo>()
// This is important because in my code I will get Any (vs Array<Foo)
var fooArrayAny : Any = foo

我希望能够从变量 fooArrayAny 中得到类型 Foo

如果我有 fooArray,我会这样做:

let type = fooArray.dynamicType.Element().dynamicType

但是,这不适用于 fooArrayAny。它说它没有成员 Element()

class Foo {
    var foo: Int = 1
}
struct Boo {
    var boo: String = "alfa"
}

func f(array: Any) {
    let mirror = Mirror(reflecting: array)
    let arraytype = mirror.subjectType
    switch arraytype {
    case is Array<Foo>.Type:
        let fooArray = array as! Array<Foo>
        print(fooArray)
    case is Array<Boo>.Type:
        let booArray = array as! Array<Boo>
        print(booArray)
    default:
        print("array is not Array<Foo> nor Array<Boo>")
        break
    }
}
var fooArray : Array<Foo> = []
fooArray.append(Foo())
var anyArray : Any = fooArray   // cast as Any
f(anyArray)                     // [Foo]
var booArray : Array<Boo> = []
booArray.append(Boo())
anyArray = booArray             // cast as Any
f(anyArray)                     // [Boo(boo: "alfa")]
var intArray : Array<Int> = []
anyArray = intArray             // cast as Any
f(anyArray)                     // array is not Array<Foo> nor Array<Boo>

如果您将 NSObject 设置为 Foo 的基础 class,那么您可以使用以下代码:

class EVReflectionTests: XCTestCase {    
    func testArrayInstance() {
        let fooArray : Array<Foo> = Array<Foo>()
        let fooArrayAny : Any = fooArray
        if let arr = fooArray as? Array {
            let i = arr.getArrayTypeInstance(arr)
            print("i = \(i)")
        }
    }
}

class Foo: NSObject {
}



extension Array {
    public func getArrayTypeInstance<T>(arr:Array<T>) -> T {
        return arr.getTypeInstance()
    }

    public func getTypeInstance<T>() -> T {
        let nsobjectype : NSObject.Type = T.self as! NSObject.Type
        let nsobject: NSObject = nsobjectype.init()
        return nsobject as! T
    }
}

这段代码是我的库的一个片段EVReflection

更新:

我注意到上面的代码中有一个错误。我使用 fooArray 而不是 fooArrayAny。将其更改为 fooArrayAny 时,我得到与您相同的错误,即编译器没有元素。在尝试了这个之后,我发现了一个可行的解决方案。它再次包含我的 EVReflection 库的部分代码。

class EVReflectionTests: XCTestCase {    
    func testArrayInstance() {
        let fooArray : Array<Foo> = Array<Foo>()
        let fooArrayAny : Any = fooArray

        if let _ = fooArrayAny as? NSArray {
            var subtype: String = "\(Mirror(reflecting: fooArrayAny))"
            subtype = subtype.substringFromIndex((subtype.componentsSeparatedByString("<") [0] + "<").endIndex)
            subtype = subtype.substringToIndex(subtype.endIndex.predecessor())
            print("The type of the array elements = \(subtype)")
            if let instance = swiftClassFromString(subtype) {
                print("An instance of the array element = \(instance)")
                let type = instance.dynamicType
                print("An instance of the array element = \(type)")
            }
        }
    }

    // All code below is a copy from the EVReflection library.

    func swiftClassFromString(className: String) -> NSObject? {
        var result: NSObject? = nil
        if className == "NSObject" {
            return NSObject()
        }
        if let anyobjectype : AnyObject.Type = swiftClassTypeFromString(className) {
            if let nsobjectype : NSObject.Type = anyobjectype as? NSObject.Type {
                let nsobject: NSObject = nsobjectype.init()
                result = nsobject
            }
        }
        return result
    }

    func swiftClassTypeFromString(className: String) -> AnyClass! {
        if className.hasPrefix("_Tt") {
            return NSClassFromString(className)
        }
        var classStringName = className
        if className.rangeOfString(".", options: NSStringCompareOptions.CaseInsensitiveSearch) == nil {
            let appName = getCleanAppName()
            classStringName = "\(appName).\(className)"
        }
        return NSClassFromString(classStringName)
    }

    func getCleanAppName(forObject: NSObject? = nil)-> String {
        var bundle = NSBundle.mainBundle()
        if forObject != nil {
            bundle = NSBundle(forClass: forObject!.dynamicType)
        }

        var appName = bundle.infoDictionary?["CFBundleName"] as? String ?? ""
        if appName == "" {
            if bundle.bundleIdentifier == nil {
                bundle = NSBundle(forClass: EVReflection().dynamicType)
            }
        appName = (bundle.bundleIdentifier!).characters.split(isSeparator: {[=11=] == "."}).map({ String([=11=]) }).last ?? ""
        }
        let cleanAppName = appName
            .stringByReplacingOccurrencesOfString(" ", withString: "_", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
            .stringByReplacingOccurrencesOfString("-", withString: "_", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
        return cleanAppName
    }
}

class Foo: NSObject {
}

此代码的输出将是:

The type of the array elements = Foo
An instance of the array element = <EVReflection_iOS_Tests.Foo: 0x7fd6c20173d0>
An instance of the array element = Foo

Swift 5

它很旧,但如果有人需要,我想分享我的版本。

我使用 ModelProtocol,我建议你使用协议,这样我们就可以通过协议进行操作来建模(例如:静态实例化)。

protocol ModelProtocol {}

class Foo: ModelProtocol {

}

因为我无法检查类型是否为数组,所以我使用 CollectionProtocol 并创建 Array 扩展以通过协议获取 Element

protocol CollectionProtocol {
    static func getElement() -> Any.Type
}
extension Array: CollectionProtocol {
    static func getElement() -> Any.Type {
        return Element.self
    }
}

测试中。

var fooArray: Array<Foo> = Array<Foo>()
var fooArrayAny: Any = fooArray

let arrayMirrorType = type(of: fooArrayAny)
String(describing: "arrayMirrorType: \(arrayMirrorType)")

if arrayMirrorType is CollectionProtocol.Type {
    let collectionType = arrayMirrorType as! CollectionProtocol.Type
    String(describing: "collectionType: \(collectionType)")

    let elementType = collectionType.getElement()
    String(describing: "elementType: \(elementType)")

    let modelType = elementType as! ModelProtocol.Type
    String(describing: "modelType: \(modelType)")

    // ... now you can do operation to modelType via ModelProtocol
}

正在打印。

arrayMirrorType: Array<Foo>
collectionType: Array<Foo>
elementType: Foo
modelType: Foo