【问题标题】:Swift - How to store in an Array subclasses of a generic typeSwift - 如何在 Array 中存储泛型类型的子类
【发布时间】:2017-04-14 20:46:10
【问题描述】:

所以,我有一个泛型(有限制)类和它的许多子类,它们在子类化时具体化泛型类型。

我想将这些子类的实例存储在一个数组中,以便它们可以被迭代并以相同的方式处理它们,但显然,没有办法从子类转换为通用超类。

这是一些说明问题的代码(您可以将其复制粘贴到操场上查看结果):

// Lets create regular classes
class Fruit {
    var text: String { return "I am some Fruit" }
}

class Apple: Fruit {
    override var text: String { return "I am an Apple" }
}
class Orange: Fruit {
    override var text: String { return "I am an Orange" }
}

// This obviously works:
let test1: Fruit = Apple()
let test2: Fruit = Orange()


// Let's create some generic class
class Tree<T: Fruit> {
    let fruit: T
    init(fruit: T) {
        self.fruit = fruit
    }
}

// Subclasses from the generic class (these work)
class AppleTree: Tree<Apple> {
    convenience init() {
        self.init(fruit: Apple())
    }
}

class OrangeTree: Tree<Orange> {
    convenience init() {
        self.init(fruit: Orange())
    }
}

// This works:
let tree: Tree<Fruit> = Tree(fruit: Apple())
tree.fruit.text               // "I am an Apple"

// This works:
let appleTree1: Tree<Apple> = AppleTree()
appleTree1.fruit.text     // "I am an Apple"

// This fails: "Cannot convert value of type 'AppleTree' to specified type 'Tree<Fruit>'
let appleTree2: Tree<Fruit> = AppleTree()

// This works:
let fruitArray: [Fruit] = [Apple(), Orange()]

// THIS IS MY GOAL:
// This fails: "Cannot convert value of type 'AppleTree' to specified type 'Tree<Fruit>'
let treeArray: [Tree<Fruit>] = [AppleTree(), OrangeTree()]


// Let's try with a generic subclass
class FruitTree<T: Fruit>: Tree<T>{}

// This works:
let genericTree: Tree<Fruit> = FruitTree(fruit: Apple())

// Let's try with a generic but more concrete subclass
class GenericOrangeTree<T: Orange>: Tree<T>{
    convenience init() {
        self.init(fruit: Orange() as! T)
    }
}

// This works:
let genericOrangeTree1 = GenericOrangeTree(fruit: Orange())
let genericOrangeTree2 = GenericOrangeTree()

// This fails: Cannot invoke initializer for type 'GenericOrangeTree<Orange>' with an argument list of type '(fruit: Orange)'
let genericTree2: Tree<Fruit> = GenericOrangeTree(fruit: Orange())

// Again, this fails: "Cannot convert value of type 'GenericOrangeTree<Orange>' to specified type 'Tree<Fruit>'
let genericTreeArray: [Tree<Fruit>] = [GenericOrangeTree()]

treeArray 变量在示例代码中说明了我想要做的事情。

我不明白为什么代码在失败时会失败。我的直觉说这应该可行,但我找不到解决此问题的方法。

TL;DR:我有一个带有一些子类的通用类,我想要一个用子类填充的通用类数组,但编译器会抱怨。

【问题讨论】:

标签: arrays swift generics subclass


【解决方案1】:

您在实现泛型之前和之后混淆了类型层次结构。使用泛型类/函数,您实际上设置了一个在编译时解析的模板(编译器宏)。

如果你说

Generic<SubClass1>
Generic<SuperClass>

这些被编译器解析为如下类型:

class Generic_SubClass1 {
  let property : SubClass1
}
class Generic_SuperClass {
  let property : SuperClass
}

解析泛型后,这两种类型没有共同的基类型,因此不能相互转换。它们是完全分开的。

不确定也没有尝试,但也许这就是你想要的:

class GenericBase {
  let property : SuperClass
}
class Generic<T: SuperClass> : GenericBase {
  final var typedProperty : T {
    get { return property as T }
    set { property = T }
  }
}

然后您可以使用GenericBase 作为共同祖先并使用动态类型来检查子类。

PS:你的代码有点难以理解,可能使用'Fruit'、'Apple'和'Orange'之类的东西——比'Superclass'、'Subclass1'、'Subclass2'更容易阅读;-)

【讨论】:

  • 谢谢,我已根据您的建议更新了代码示例,以便于理解。
【解决方案2】:

也许您可以定义一个名为FruitTree 的协议,它可以获取一个水果并返回某种水果:

protocol FruitTree {
    associatedType FruitKind
    func getFruit() -> Fruit
    func setFruit(FruitKind) 
}

然后,定义类如下:

class AppleTree: FruitTree {
   var apple Apple
   typeAlias FruitKind = Apple
   func getFruit() -> Apple {return apple}
   func setFruit(Apple a} {apple = a}
}
class OrangeTree: FruitTree {
   var orange Orange
   typeAlias FruitKind = Orange
   func getFruit() -> Orange { return orange}
   func setFruit(Orange o) {orange= o}
}
let treeArray: [FruitTree] = [AppleTree(), OrangeTree()]

【讨论】:

  • 那行不通。您只能使用定义关联类型的协议来限制泛型和其他关联类型中的类型。不定义一个变量的类型。不过感谢您的回复!
【解决方案3】:

我认为您正在寻找的是类型擦除。

例如,成像您有以下内容:

protocol ClassWithView {
    associatedType: View: UIView
    var view: View { get set }
}

class ConcreteWithView {
    associatedType View = SubclassOfUIView 
    var view: SubclassOfUIView
}

// Somewhere this will fail because there is missing associated type information
var array: [ConcreteWithView]

通过类型擦除,您可以强制对数组的任何访问都只会让您访问常见的 UIView 内容。修改上述内容:

protocol AnyClassWithView {
      var baseView: UIView
}

protocol ClassWithView: AnyClassWithView {
    associatedType: View: UIView
    var view: View { get set }
}

// Default implementation
extension AnyClassWithView {
    var baseView: UIView {
       return view as UIView
    }
}

现在,您可以在其他地方定义数组,如:

var array: [AnyClassWithView]

哪个会成功,并且您只能使用.baseView 访问 UIView 类型。您可以将内容添加到 AnyClassWithView 定义并使用共享的 UIView 进行操作。

如果您想访问各个子类型,请在此创建一个接受泛型参数的函数,您应该能够强制转换以访问子类型信息。

【讨论】:

    猜你喜欢
    • 2018-05-13
    • 1970-01-01
    • 2017-07-27
    • 1970-01-01
    • 1970-01-01
    • 2018-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多