5

So, I have a generic (with restrictions) class and a number of subclasses of it that concrete the generic type when they subclass.

I want to store instances of these subclasses in an array so then they can be iterated and treated all of them in the same way, but apparently, there is no way to convert from the subclass to the generic superclass.

Here is some code that illustrates the problem (you can copy-paste it in a playground to see the outcome):

// 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()]

What I am trying to do is illustrated in the sample code by the treeArray variable.

I don't understand why the code fails when it fails. My intuition says that this should work and I cannot find a work around this problem.

TL;DR: I have a Generic class with some subclasses, I want to have an array of the Generic class filled with the subclasses, but the compiler complains.

Kinopio
  • 176
  • 9
  • Well your `SubclassOfGeneric1` and `SubclassOfGeneric2` doesn't really have common parent, because each `Generic` is actually compiles into separate class for each `T` used in code. So `Generic` and `Generic` and `Generic` are three unrelated classes with similar interface. EDIT: Well, Swift generic classes compilation process is quite complex, but you should always expect that it woul compile into separate classes. http://stackoverflow.com/questions/25917428/are-swift-generics-separately-compiled – user28434'mstep Nov 30 '16 at 13:55
  • Possible duplicate of [How do I store a value of type Class in a Dictionary of type \[String:Class\] in Swift?](http://stackoverflow.com/questions/38590548/how-do-i-store-a-value-of-type-classclassimplementingprotocol-in-a-dictionary) – Hamish Nov 30 '16 at 15:01

3 Answers3

0

You are mixing up the type hierarchies before and after Generics have been materialized. With a generic class/func you essentially setup a template (a compiler macro) which is resolved at compile-time.

If you say

Generic<SubClass1>
Generic<SuperClass>

those are resolved by the compiler to types like:

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

After the generics are resolved those two types share no common base type and hence can't be cast to each other. They are completely separate.

Not sure and didn't try, but maybe this is what you want:

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

You can then use GenericBase as the common ancestor and use dynamic type to check for subclasses.

P.S.: Your code is a little hard to follow, maybe use something like 'Fruit', 'Apple' and 'Orange' - much easier to read than 'Superclass', 'Subclass1', 'Subclass2' ;-)

hnh
  • 13,957
  • 6
  • 30
  • 40
0

I think what you are looking for is type erasure.

For example, imaging you have the following:

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]

With type erasure you can enforce that any access to the array will only let you access the common UIView stuff. Modifying the above like:

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
    }
}

Now, elsewhere you can define the array like:

var array: [AnyClassWithView]

Which will succeed, and you can access only the UIView type with .baseView. You can add stuff to the AnyClassWithView definition and do stuff with the shared UIView.

If you want access to the individual subtypes, create a function on this that accepts a generic parameter and you should be able to cast to access the subtype information.

Andrew Plummer
  • 1,150
  • 1
  • 12
  • 21
-2

Perhaps you could define a protocol named FruitTree that can get a Fruit and return some kind of Fruit:

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

Then, define classes something like:

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()]
Paul Buis
  • 805
  • 7
  • 7
  • 2
    That doesn't work. You can only use protocols that define associated types for restricting types in generics and other associated types. Not to define one variable's type. Thanks for the reply, though! – Kinopio Dec 01 '16 at 12:48