I am running into a problem with Swift protocols. I have something like this:
protocol OnboardingPage {
var viewModel: OnboardingPageViewModel! { get }
}
protocol OnboardingPageViewModel {
func getValue() -> Bool
}
// ---
class FirstNameViewController: UIViewController, OnboardingPage {
var viewModel: FirstNameViewModel!
}
class FirstNameViewModel: OnboardingPageViewModel {
func getValue() -> Bool {
return true
}
}
class GenderViewController: UIViewController, OnboardingPage {
var viewModel: GenderViewModel!
}
class GenderViewModel: OnboardingPageViewModel {
func getValue() -> Bool {
return false
}
}
I have a bunch of view controllers that confirm to the OnboardingPage protocol, and they all have a view model that all conform to the OnboardingPageViewModel protocol.
But sadly the code above doesn't work: it says FirstNameViewController doesn’t confirm to the OnboardingPage protocol, since its viewModel property has type FirstNameViewModel instead of OnboardingPageViewModel - even though FirstNameViewModel IS a OnboardingPageViewModel.
How can I solve this?
Use associated type OnboardingPageViewModel type ViewModel
Edited
protocol OnboardingPage {
associatedtype ViewModel: OnboardingPageViewModel
var viewModel: ViewModel { get }
}
protocol OnboardingPageViewModel {
func getValue() -> Bool
}
class FirstNameViewController: OnboardingPage {
let viewModel: FirstNameViewModel
init(viewModel: FirstNameViewModel) {
self.viewModel = viewModel
}
}
class FirstNameViewModel: OnboardingPageViewModel {
func getValue() -> Bool {
return true
}
}
class GenderViewController: OnboardingPage {
let viewModel: GenderViewModel
init(viewModel: GenderViewModel) {
self.viewModel = viewModel
}
}
class GenderViewModel: OnboardingPageViewModel {
func getValue() -> Bool {
return false
}
}
You may solve your pages array issue with deletion of associatedtype:
protocol OnboardingPage {
var viewModel: OnboardingPageViewModel { get }
}
protocol OnboardingPageViewModel {
func getValue() -> Bool
}
class FirstNameViewModel: OnboardingPageViewModel {
func getValue() -> Bool {
return true
}
}
class FirstNameViewController: OnboardingPage {
let viewModel: OnboardingPageViewModel
init(viewModel: FirstNameViewModel) {
self.viewModel = viewModel
}
}
class GenderViewModel: OnboardingPageViewModel {
func getValue() -> Bool {
return false
}
}
class GenderViewController: OnboardingPage {
let viewModel: OnboardingPageViewModel
init(viewModel: GenderViewModel) {
self.viewModel = viewModel
}
}
Usage:
let nameModel = FirstNameViewModel()
let genderModel = GenderViewModel()
let array: [OnboardingPage] = [FirstNameViewController(viewModel: nameModel), GenderViewController(viewModel: genderModel)]
for page in array {
print(page.viewModel.getValue())
}
However, in such manner concrete types of your models will not be available for callers of OnboardingPage conforming classes. You may try to solve it with implementing visitor pattern within OnboardingPage or enhancing its capabilities.
Of course, you can always write let pages: [Any] = [FirstNameViewController(), GenderViewController()] using Cruz example, but it is ugly as it goes, and I strongly discourage you from using Any in your API if possible.
Related
why does typealias closure not transmit data and output nothing to the console? How to fix it?
class viewModel: NSObject {
var abc = ["123", "456", "789"]
typealias type = ([String]) -> Void
var send: type?
func createCharts(_ dataPoints: [String]) {
var dataEntry: [String] = []
for item in dataPoints {
dataEntry.append(item)
}
send?(dataEntry)
}
override init() {
super.init()
self.createCharts(abc)
}
}
class ViewController: UIViewController {
var viewModel: viewModel = viewModel()
func type() {
viewModel.send = { item in
print(item)
}
}
override func viewDidLoad() {
super.viewDidLoad()
print("hello")
type()
}
}
I have a project in which a similar design works, but I can not repeat it
The pattern is fine, but the timing is off.
You’re calling createCharts during the init of the view model. But the view controller is setting the send closure after the init of the view model is done.
Bottom line, you probably don’t want to call createCharts during the init of the view model.
Possible solution is to create custom initializer:
class viewModel: NSObject {
...
init(send: type?) {
self.send = send
self.createCharts(abc)
}
}
class ViewController: UIViewController {
var viewModel: viewModel = viewModel(send: { print($0) })
...
}
Is there a way to achieve this ?
protocol VCProtocol1: UIViewController {
var viewModel: VMProtocol1? { get set }
}
protocol VCProtocol2: UIViewController {
var viewModel: VMProtocol2? { get set }
}
class VC: UIViewController, VCProtocol1, VCProtocol2 {
var viewModel: (VMProtocol1 & VMProtocol2)?
}
What I want to do is composition on ViewController to avoid re-implementing the same code in multiple ViewControllers.
Edit:
To be more precise, the problem here is I want my viewModel property to be shared between both protocols because ultimately I want to implement something like this:
- View Model
protocol VMProtocol1 {
func vmTest1()
}
protocol VMProtocol2 {
func vmTest2()
}
class ViewModel: VMProtocol1, VMProtocol2 {
func vmTest1() { }
func vmTest2() { }
}
- View Controller
protocol VCProtocol1: UIViewController {
var viewModel: VMProtocol1 { get set }
func vcTest1()
}
extension VCProtocol1 {
func vcTest1() {
// This is the key point, I want to be able to refer to my viewModel property internally in each protocol.
self.viewModel.vmTest1()
}
}
protocol VCProtocol2: UIViewController {
var viewModel: VMProtocol2 { get set }
}
class VC: UIViewController, VCProtocol1, VCProtocol2 {
var viewModel: (VMProtocol1 & VMProtocol2)?
}
This is actually not possible. One entity that implements multiple protocols which declare the same property (here viewModel) but with a different type is simply not possible in Swift.
The closest you can do is to use a protocol which define the viewModel type as an associated type and provide conditional extensions (it will force you to use a generic implementation).
Please note that this is a complex implementation that require a good knowledge of generic protocols. I would not recommend using a such complicated design without understanding exactly what you are doing.
View model
protocol VMProtocol1 {
func vmTest1()
}
protocol VMProtocol2 {
func vmTest2()
}
class ViewModel: VMProtocol1, VMProtocol2 {
func vmTest1() { }
func vmTest2() { }
}
Implementation
protocol VCProtocolBase {
associatedtype ViewModel
var viewModel: ViewModel { get }
}
protocol VCProtocol1: VCProtocolBase {
func vmTest1()
}
protocol VCProtocol2: VCProtocolBase {
func vmTest2()
}
extension VCProtocolBase where Self: VCProtocol1, Self.ViewModel: VMProtocol1 {
func vmTest1() {
viewModel.vmTest1()
}
}
extension VCProtocolBase where Self: VCProtocol2, Self.ViewModel: VMProtocol2 {
func vmTest2() {
viewModel.vmTest2()
}
}
final class VC<ViewModel: VMProtocol1 & VMProtocol2>: VCProtocolBase, VCProtocol1, VCProtocol2 {
var viewModel: ViewModel
init(viewModel: ViewModel) {
self.viewModel = viewModel
}
}
VC(viewModel: ViewModel()).vmTest1()
VC(viewModel: ViewModel()).vmTest2()
The idea here is to rely on generics and conditional extensions to provide the default implementations for the VCProtocolX, which is the role of VCProtocolBase.
I have something like this:
protocol ViewModel: class {
var eventWithInitialValue: Observable<Int> { get }
}
class ViewModelImpl: ViewModel {
let eventWithInitialValue: BehaviorSubject<Int> = BehaviorSubject(value: 0)
init() {
eventWithInitialValue.onNext(1)
}
}
class ViewController: UIViewController {
weak var viewModel: ViewModel?
private let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
viewModel?
.eventWithInitialValue
.subscribe(onNext: {
print($0)
}).disposed(by: bag)
}
}
I want to communicate with viewModel fields from ViewController as an Observables. But inside viewModel this fields should be a [OneOf]Subject types (for safety reasons).
Implementation above have a next compile time error -> Type 'ViewModelImpl' does not conform to protocol 'ViewModel'
Can anyone help with the implementation of these requirements?
Your problem is not with Rx, your error is related to your protocol
This will solve the current issue
protocol ViewModel: class {
var eventWithInitialValue: BehaviorSubject<Int> { get }
}
class ViewModelImpl: ViewModel {
var eventWithInitialValue: BehaviorSubject<Int> = BehaviorSubject(value: 0)
init() {
eventWithInitialValue.onNext(1)
}
}
I think you got this Type 'ViewModelImpl' does not conform to protocol 'ViewModel' because you define the eventWithInitialValue's type in your implementation as BehaviorSubject.
What I can suggest is something like this
protocol ViewModel {
var data: Observable<Int> { get}
}
class ViewModelImpl: ViewModel {
private let dataSubject = BehaviorSubject(value: 1)
var data: Observable<Int> {
return dataSubject
}
}
I am trying to implement generic for MVVM Swift. I have two base class, an protocol for generic class. The special in here is inheritance. I tried for three hour but I can't fix it :(.
protocol ObjectProtocol {
var id: Int { get set }
var name: String { get set }
}
class BaseViewModel<T: ObjectProtocol> {
var objects: [T] = []
init(){
}
}
protocol ListViewControllerType {
associatedtype T: ObjectProtocol
associatedtype ViewModelType: BaseViewModel<T>
var viewModel: ViewModelType! { get set }
func showError(error: String)
}
extension ListViewControllerType {
func showError(error: String) {
print(error)
}
}
class Consult: ObjectProtocol {
var id: Int = 1
var name: String = "Consult"
}
class ConsultViewModel<T: Consult>: BaseViewModel<Consult> {
}
class ConsultViewController: ListViewControllerType {
var viewModel: ConsultViewModel<Consult>!
}
But I get error in var viewModel: ConsultViewModel<Consult>!
This is error: Type 'ConsultViewController' does not conform to protocol 'ListViewControllerType'
Someone have experience with generic and inheritance can help me please.
Thank you so much.
I usually do like this:
class BaseViewModel {
}
class TemplateViewModel<T: ObjectProtocol>: BaseViewModel {
var objects: [T] = []
init(){
}
}
I am trying to make following code compile.
Basically I want to have object conforming multiple protocols, for example network service that exposes different groups of API to different parts of app (for example: login API to loginController, sharing API to shareController)
+
I want this service to be injected using networkXProvider protocols e.g. implement Dependency Injection (for example I want to pass to LoginController an Injection that adopts LoginControllerProvider and FetchingUsersFromDBProvider):
protocol MyAccountCommunicator {
func getAccountData() -> String
}
protocol EventsCommunicator {
func getEvents() -> String
}
class NetworkManager: MyAccountCommunicator, EventsCommunicator {
func getAccountData() -> String {
return "Accounts"
}
func getEvents() -> String {
return "Events"
}
}
protocol MyAccountCommunicatorProvider {
var networkCommunicator: MyAccountCommunicator { get }
}
protocol EventsCommunicatorProvider {
var networkCommunicator: EventsCommunicator { get }
}
class Injector: MyAccountCommunicatorProvider, EventsCommunicatorProvider {
var networkCommunicator: NetworkManager = NetworkManager()
}
Playground fails with following error:
error: strings.playground:29:7: error: type 'Injector' does not
conform to protocol 'MyAccountCommunicatorProvider' class Injector:
MyAccountCommunicatorProvider, EventsCommunicatorProvider {
^
strings.playground:30:9: note: candidate has non-matching type
'NetworkManager'
var networkCommunicator: NetworkManager = NetworkManager()
^
error: strings.playground:29:7: error: type 'Injector' does not
conform to protocol 'EventsCommunicatorProvider' class Injector:
MyAccountCommunicatorProvider, EventsCommunicatorProvider {
^
strings.playground:30:9: note: candidate has non-matching type
'NetworkManager'
var networkCommunicator: NetworkManager = NetworkManager()
Environment: swift4, XCode9
Why do you need MyAccountCommunicatorProvider and EventsCommunicatorProvider protocols? What if you combine them into one?
protocol CommunicatorProvider {
var networkCommunicator: NetworkManager { get }
}
class Injector: CommunicatorProvider {
var networkCommunicator = NetworkManager()
}
protocol MyAccountCommunicator {
func getAccountData() -> String
}
protocol EventsCommunicator {
func getEvents() -> String
}
protocol MyAccountCommunicatorProvider {
var accountCommunicator: MyAccountCommunicator { get }
}
protocol EventsCommunicatorProvider {
var eventCommunicator: EventsCommunicator { get }
}
class NetworkManager: MyAccountCommunicator, EventsCommunicator {
func getAccountData() -> String {
return "Accounts"
}
func getEvents() -> String {
return "Events"
}
}
class Injector: MyAccountCommunicatorProvider, EventsCommunicatorProvider {
var networkManager = NetworkManager()
var accountCommunicator: MyAccountCommunicator {
return networkManager
}
var eventCommunicator: EventsCommunicator {
return networkManager
}
}
You can use like this :
protocol MyAccountCommunicatorProvider {
var networkCommunicator1: MyAccountCommunicator { get }
}
protocol EventsCommunicatorProvider {
var networkCommunicator2: EventsCommunicator { get }
}
class Injector: MyAccountCommunicatorProvider, EventsCommunicatorProvider {
var networkCommunicator: NetworkManager = NetworkManager()
lazy var networkCommunicator1: MyAccountCommunicator = {
return networkCommunicator
}()
lazy var networkCommunicator2: EventsCommunicator = {
return networkCommunicator
}()
}