Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift 2 Protocol - Property with a Generic Type?

Tags:

swift2

In my project using Swift 2, I am dealing with 2 protocols, MyViewControllerProtocol and MyViewModelProtocol. I want all View Controllers to conform to MyViewControllerProtocol. This protocol will require a property. The property should conform to the MyViewModel protocol.

What I was thinking would work here would be something like this:

protocol MyViewControllerProtocol {
    var viewModel: <T:MyViewModelProtocol> { get set }
} 

class MyCustomViewModel: MyViewModelProtocol {
     // Implementation here
}

Then for the View Controller:

class ViewController: UIViewController, MyViewControllerProtocol {
    var viewModel: MyCustomViewModel {
        // Getter and Setter implementations here
    }
}

I am most likely thinking about this wrong. This will not compile, and I haven't yet seen this type of implementation in conjunction with a property. Is there some other pattern that could accomplish what I'm trying to do here?

like image 358
Mark Struzinski Avatar asked Feb 09 '23 10:02

Mark Struzinski


2 Answers

If you would like to have dynamic protocol property type, there's typealias.

protocol MyViewModel {
  var title: String { get set }
}

protocol MyViewController {
  typealias MyViewModelType

  var viewModel: MyViewModelType { get set }
}

class BaseViewController<T: MyViewModel>: MyViewController {
  typealias MyViewModelType = T
  var viewModel: T

  init(_ viewModel: T) {
    self.viewModel = viewModel
  }
}

struct CarViewModel: MyViewModel {
  var title: String = "Car"
}

struct BikeViewModel: MyViewModel {
  var title: String = "Bike"
}

let car = BaseViewController(CarViewModel())
let bike = BaseViewController(BikeViewModel())

If you try to use it with model not conforming to your MyViewModel protocol, it will not work:

struct AnotherModel {
  var title: String = "Another"
}

let another = BaseViewController(AnotherModel())

This has some gotchas like you can't pass your view controller via argument of MyViewController type, because of typealias. This is not going to work:

func something(vc: MyViewController) {
}

Why not simpler approach without typealias. If I understood you correctly, you don't need them. Something like:

protocol MyViewModel {
  var title: String { get set }
}

protocol MyViewController {
  var viewModel: MyViewModel { get set }
}

class BaseViewController: MyViewController {
  var viewModel: MyViewModel

  init(_ viewModel: MyViewModel) {
    self.viewModel = viewModel
  }
}

struct CarViewModel: MyViewModel {
  var title: String = "Car"
}

struct BikeViewModel: MyViewModel {
  var title: String = "Bike"
}

And now you can use MyViewController protocol as a variable type:

let bike: MyViewController = BaseViewController(BikeViewModel())
let car: MyViewController = BaseViewController(CarViewModel())

You can pass it to some function as MyViewController:

func something(vc: MyViewController) {
}

And you can't do this as well:

struct AnotherViewModel {
  var title: String = "Another"
}

let another: MyViewController = BaseViewController(AnotherViewModel())

Did I miss something? I mean, about your generics and type constraints? Do you have some use case which forces you to use them?

like image 186
zrzka Avatar answered Feb 24 '23 15:02

zrzka


typealias for protocols has been deprecated and replaced with associatedtype. The above answers would now be written like:

protocol MyViewController { 
    associatedtype MyViewModelType
    var viewModel: MyViewModelType { get set }
}
like image 32
davidrynn Avatar answered Feb 24 '23 15:02

davidrynn