3

I have the model object:

class Animal {
// ...
}

And subclasses:

class Dog: Animal {
// ...
}

class Cat: Animal {
// ...
}

Also I've created the generic class

class AnimalController<T: Animal> {
      var animal: T? 
      func feed(animal: T) {
          let food = Food(T.self)
          animal.feed(food)
      }
}

Here it's the issue:

class MainViewController: UIViewController {
      var controller: AnimalController<Animal>?

    // MARK: - Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()

        // I want control a dog
        self.controller = AnimalController<Dog>()  // Error

        // I want control a cat
        self.controller = AnimalController<Cat>()  // Error
    }
}

How could I create generic class compatible with dogs and cats? Thanks!

UPDATED Hamish give me the solution linking other two posts.

I have the model object:

class Animal {
// ...
}

And subclasses:

class Dog: Animal {
// ...
}

class Cat: Animal {
// ...
}

Also I've created the generic class

class AnimalController<T> {
    var type: T

    init(type: T) {
        self.type = type
    }

    func feed() {
        if type is Animal.Type {
            let food = Food(type as! Animal.Type)
            animal.feed(food)
        }
    }
}

Now it works:

class MainViewController: UIViewController {
      var controller: AnimalController<Animal>?

    // MARK: - Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()

        // I want control a dog
        self.controller = AnimalController<Dog>()  // Works!
        self.controller = AnimalController<Cat>()  // Works!
    }
}
torcelly
  • 516
  • 4
  • 8
  • Your classes are not very generic:-) I suggest you read the chapter about generics in Apple's documentation – user3441734 Jun 04 '17 at 13:01
  • Wrong spelling. Sorry. Edited. – torcelly Jun 04 '17 at 13:32
  • 1
    The problem is that generics are invariant – compare https://stackoverflow.com/q/41976844/2976878 & https://stackoverflow.com/questions/38590548/how-do-i-store-a-value-of-type-classclassimplementingprotocol-in-a-dictionary – Hamish Jun 04 '17 at 13:36
  • There's no possible way your edited code could work – consider if you were able to assign a `AnimalController` to a `AnimalController`. You could then say `controller?.feed(animal: Cat())`. You've now just fed dog food to a cat. – Hamish Jun 04 '17 at 13:48
  • Thanks Hamish. I read the links and give me the solution. I edit my question adding the answer. – torcelly Jun 04 '17 at 14:07

3 Answers3

0

Swift is a strong type language. You have already declared your property controller, and you can't change its type, but that's entirely okay, because of Swift supports a generalization-paradigm, that allows you to save objects in a property with a common superclass.

Rainer Niemann
  • 454
  • 3
  • 16
0

This looks like a case where inheritance should be utilized instead of generics.

class AnimalController {
    // basic animal control tasks
}


class CatController: AnimalController {
    // specialized controller just for cats
}
class DogController: AnimalController {
    // ...
}

Then you could just write

class MainViewController: UIViewController {
    var animalController: AnimalController

    override func viewDidLoad() {
        super.viewDidLoad()

        // I want control a dog
        self.controller = DogController()

        // I want control a cat
        self.controller = CatController()
    }
}

Working with generics can lead to problems in this case as the Swift compiler resolves generic types and replaces them with concrete types (which is not possible unless you make your whole view controller generic but that would work well with interface builder)

Palle
  • 11,511
  • 2
  • 40
  • 61
  • There isn't a difference between cats/dogs in its controller. It would be the same using your code and we have duplicate code. The difference is in the model. Obviously, cats and dogs have different properties. Thanks – torcelly Jun 04 '17 at 13:35
  • If that is the case, you just need the animal controller, not the two subclasses. The rest stays identical. – Palle Jun 04 '17 at 18:11
0

In your example, there's no need to make AnimalController a generic class. It can simple have methods that work on Animals and they will work on both Dog and Cat via inheritance.

class Animals {
    // ...
}

class Dog: Animals {
    // ...
}

class Cat: Animals {
    // ...
}

class AnimalController {
    // methods to control any kind of animals
    var animal: Animals? // Will take both cats and dogs
    func feed(animal: Animals) {
        // Will take both cats and dogs
    }
}

class MainViewController: UIViewController {
    var controller: AnimalController?

    // MARK: - Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()

        // I want control a dog or a cat
        self.controller = AnimalController()

        let cat = Cat()
        controller?.animal = cat
        controller?.feed(animal: cat)

        let dog = Dog()
        controller?.animal = dog
        controller?.feed(animal: dog)
    }
}

(updated)

Even after your edits, I see no need for generics:

class Animal {
    // ...
    func feed(_ food: Food){
        // feed the animal or maybe throw an error
        // if subclasses must implement
    }
}

class Dog: Animal {
    // ...
    override func feed(_ food: Food) {
        // feed a cat
    }
}

class Cat: Animal {
    // ...
    override func feed(_ food: Food) {
        // feed a dog
    }
}

class Food {
    init(_ animalType: Animal.Type) {
        // Make food
    }
}

class AnimalController {
    // methods to control any kind of animals
    var animal: Animal? // Will take both cats and dogs
    func feed(animal: Animal) {
        // Will take both cats and dogs
        let food = Food(type(of: animal))
        animal.feed(food)
    }
}

class MainViewController: UIViewController {
    var controller: AnimalController?

    // MARK: - Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()

        // I want control a dog or a cat
        self.controller = AnimalController()

        let cat = Cat()
        controller?.animal = cat
        controller?.feed(animal: cat)

        let dog = Dog()
        controller?.animal = dog
        controller?.feed(animal: dog)
    }
}
Matusalem Marques
  • 2,399
  • 2
  • 18
  • 28
  • First of all, thanks for your answer. It makes sense but the "feed" method must be different between cats and dogs in my original code. I've added the method in my explanation to expose the problem clearer. The original method use Realm object class with that pattern, but I didn't want complicate unnecessary the question add many lines of code. – torcelly Jun 04 '17 at 13:44
  • The difference between feed cat and dogs just a parameter Cat.self or Dog.self sent to other object in the algorithm. If I override the method will duplicate many lines of code. – torcelly Jun 04 '17 at 14:23