如何检查“Any”类型的对象是实现某些协议的具体类的数组

时间:2017-04-19 16:34:22

标签: arrays swift generics

我遇到了一个问题。 考虑我有一个协议和两个实现它的类:

protocol Initiatable{
    init()
}

class A: Initiatable{
    required init() {}
}

class B: Initiatable{
    required init() {}
}

然后在某些时候我创建了一个数组并将其传递给函数:

var array = [A]()

func update(object: Any){

}

update(object: array)

来自该函数update如果它符合另一个函数的条件,我想将object传递给另一个函数:

func process<T: Initiatable>(array: T){
    /* ... */
}

那么如何检查Any类型的对象是否是实现Initiatable协议的具体类的数组?我想写点像

func update(object: Any){
    if let array = object as Array<T: Initiatable>{
        process(array: array)
    }
}

但那并不奏效。代码如:

func update(object: Any){
    if let array = object as [Initiatable]{
        process(array: array)
    }
}

func process(array: [Initiatable]){ }

编译很好,但这不是我想要的 - process函数应该收到Initiatable的具体实现的数组,所以在某些时候它可以使用:

func process<T: Initiatable>(array: [T]){
    /* other code */
    T.init()
}

有没有办法做到这一点?非常感谢你提前!

5 个答案:

答案 0 :(得分:3)

这个问题有几个部分:

生成类型数组

您的数组声明需要一组A 个对象而不是A 类型。要生成包含A类型的数组,您可以传入Postfix self表达式:(link

var array = [ A.self ]

这会将array定义为A.Type的数组,称为元类型类型(same link)。

您还可以使用此元类型类型生成空数组:

var array:[A.Type] = []

如果您想要一个同时包含A.selfB.self的数组,您可以将其指定为[Any] ...

var array:[Any] = [A.self,B.self]

...或者,使用您创建的Initiatable协议:

var array:[Initiatable.Type] = [A.self,B.self]

在更新方法中将数组向下转换为类型数组

您无法将Any对象向下转换为类型数组。 这是我更新的update方法:

func update(object: Any){
  if let array = object as? [Initiatable.Type] {    //1
    process(array: array)
  }
}
  1. 您现在可以对正在过去的update方法的数组执行可选的向下转换。将其向下转换为Initiatable的元数据类型数组:(这是我从您的方法修改的唯一行)
  2. 在流程方法中接收类型作为参数

    我假设您只希望您的流程方法接收类型数组,并基于其中一种类型实例化变量。你没有提到数组中的哪个元素,所以我刚刚使用了第一个元素。

    func process(array: [Initiatable.Type]){    //1
      if let firstType = array.first {          //2
        let firstObject = firstType.init()      //3
      }
    }
    
    1. process方法可以接收采用Initiatable协议的类型数组。
    2. 我正在使用可选绑定来获取数组中第一个元素的值。这应该是一种类型。
    3. 根据数组中的第一个元素实例化对象。

答案 1 :(得分:1)

如果你的数组是非空的,那么你可以通过从数组中获取一个元素的运行时类型来解决这个问题:

func update(object: Any){
    if let array = object as? [Initiatable]{
        process(array: array)
    } else {
        // Not initiatable
    }
}

func process(array: [Initiatable]) {  // no need for generics here
    guard let first = array.first else {
        // Unable to determine array type
        // throw or return or whatever
    }

    // type(of: first) gives A or B
    type(of: first).init()
}

如果数组是空的,那么我不知道在Swift中这样做的方法(至于Swift 3,无论如何)。

  • object的静态类型为Any,因此您需要使用运行时类型的object才能到达任何地方。
  • 但是,Swift(以及大多数使用泛型的语言)仅根据您传递的值的 static 类型解析类型参数,例如T。因此,使用process<T: Initiatable>(array: [T])的整个方法都是死路一条:T只能在调用process()时使编译器已知的类型
  • Swift确实允许您访问运行时类型:type(of: object)将返回(例如)Array<A>。很近!但:
    • AFAIK,目前无法从元类型中提取类型参数,即从A获取Array<A>,除非编译器已经静态地知道类型参数。
    • 可以获取字符串"Array<A>"并从中提取字符串A,但是AFAIK没有办法将字符串映射回Swift中的类型,除非类型是一个Objective-C类。

简而言之,我认为你刚刚达到Swift的元类型/反射能力的极限。

答案 2 :(得分:0)

不幸的是,我无法在给定的约束条件下考虑解决方案。看起来你正试图在运行时实现编译时多态性。

使用泛型意味着编译器了解将调用哪个类。但是使用Any意味着在编译时它可以是任何东西,只有在运行时才知道。

我可以提出的最接近的解决方案是:

// Either use generic `update` function
func updateGeneric<T: Initiatable>(array: Array<T>){
    process(array: array)
}

// or explicitly convert `Any` to the class, 
// so compiler understands what implementation of `process` to call
func updateExplicit(object: Any) {

    if let array = object as? [A] {
        process(array: array)
    }

    if let array = object as? [B] {
        process(array: array)
    }
}

答案 3 :(得分:0)

这对你有帮助吗,

protocol Initiatable{
    init()
}

class A: Initiatable{
    required init() {}
}

class B: Initiatable{
    required init() {}
}

class Test<T : Initiatable>
{

    func update(object: Any){
        if let array = object as? [T]{
            process(array: array)
        }
    }

    func process(array: [T]){

    }
}

答案 4 :(得分:0)

我找到了解决此问题的解决方法:

func classTypeFrom(_ className: String) -> AnyClass!{
    if  let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String {
        let classStringName = "_TtC\(appName.characters.count)\(appName)\(className.characters.count)\(className)"
        return NSClassFromString(classStringName)
    }
    return nil;
}

func update(object: Any){
    if let array = object as? [Initiatable]{
        let arrayTypeName = "\(type(of: ar))"
        let objectTypeName = arrayTypeName.substringFrom(index: 6, length: arrayTypeName.characters.count - 7)
        if let arrayType = classTypeFrom(objectTypeName) as? Initiatable.Type{
            process(array: array, type: arrayType)
        }
    }
}

func process(array: [Initiatable], type: Initiatable.Type){
    var ar = array
    let newObj = type.init()
    ar.append(newObj)
}