How can my Protocol oriented to specify ViewModel? - swift

I have a problem from Protocol oriented ViewModels.
I have two very similar pages,so i made a baseController and baseViewModel to put shared properties and methods. And made two other controllers and viewModels for two pages.
And I made a protocol to define the properties and methods.
My baseController has var viewModel: BaseViewModelProtocol .
But my other two controller cannot use the properties and methods from their viewModel, it's says
Value of type 'BaseViewModelProtocol?' has no member ''
ViewModel1 is for Controller1, ViewModel2 is for Controller2, here is my example
protocol BaseViewModelProtocol {
var name: String { get }
func reset()
}
class BaseViewModel: BaseViewModelProtocol {
func reset() { }
}
class ViewModel1: BaseViewModel {
var score: Int = 0
func someMethods() {}
}
class ViewModel2: BaseViewModel {
var money: Int = 1000
func something() {
print("something")
}
}
class BaseViewController: UIViewController {
var viewModel: BaseViewModelProtocol?
init(viewModel: BaseViewModelProtocol) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
}
class ViewController1: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(viewModel?.score) //it is error!!!!
}
}
When i init the Controller, i Cannot use any properties and methods from ViewModel2, and the same like controller1.
class ViewController2: BaseOrderViewController {
override func viewDidLoad() {
super.viewDidLoad()
viewModel?.something(). //it is error!!!!
}
}
how to my protocol oriented viewModel?

After reading your post I feel like there is too many things that should be addressed here.
You are trying to ask something before understanding important characteristics of the Object Oriented Programmation paradigm. I recommend you to try and search information about class abstraction, composition vs inheritance and specificaly how to use the protocols in Swift.
Maybe you could start with https://cocoacasts.com/how-to-create-an-abstract-class-in-swift.
Even so, i am going to try to point out some issues in your code.
Seems like you tried to "hide" your source code by changing your classes and properties' names. The way it's done makes it harder to read, understand and introduces mistakes.
Inheriting from another class:
class ViewModel1: BaseViewModel
it's not the same as inheriting from a protocol:
class BaseViewModel: BaseViewModelProtocol
while the first one provides you a default implementation of a method that can be overrided, the second, doesn't. So it is mandatory to provide the implementation of the method in every class that inherits the protocol.
Note that the BaseViewController has a property with type BaseViewModelProtocol. That protocol doesn't have a method called "something" neither can inherit that function from another. It's within reason that the compiler shows the error.
If you want to use "something" from a Class that inherits BaseViewController, you have many ways. You could change the type of the viewModel in the BaseViewController to BaseViewModel after adding there an implementation of "something". You could also add the function to the protocol and make sure that it's implemented in all of the classes which inherit the protocol...
I hope I have helped you.

Related

Implementing MVP using protocols

I'm trying to implement MVP using protocols,
I have View controller that holds a reference to a presenter protocol.
The presenter protocol contains a reference to the view
and has associatedtype that represent the ResultType.
The ResultType is different at each presenter.
for example:
class PresenterA: PresenterProtocol {
weak var view: ViewController!
typealias ResultType = String
var onDidPressCallback: ((ResultType) -> Void)?
}
It also can be
class PresenterB: PresenterProtocol {
weak var view: ViewController!
typealias ResultType = Apple
var onDidPressCallback: ((ResultType) -> Void)?
}
The problem start's when Im holding a reference to the presenter
from the ViewController.
class ViewController: UIViewController {
var presenter: PresenterProtocol!
}
Of course It is not possible and I get this error:
Protocol 'PresenterProtocol' can only be used as a generic constraint
because it has Self or associated type requirements
So I tried:
class ViewController<T: PresenterProtocol>: UIViewController {
var presenter: T!
}
But now the PresenterProtocol has this issue:
Reference to generic type 'ViewController' requires arguments in <...>
What am I doing wrong? And how can I solve it?
Plus, let's say I can not support new versions so I cannot use opaque type (some keyword).
In answer to your question, when you make the view controller generic, the error says that the “generic type 'ViewController' requires arguments in <...>”. So you can do precisely that:
class PresenterA: PresenterProtocol {
weak var view: ViewController<PresenterA>!
...
}
You will probably end up wanting to make a required initializer so you can instantiate these presenters, e.g.:
protocol PresenterProtocol {
associatedtype ResultType
var onDidPressCallback: ((ResultType) -> Void)? { get set }
init()
}
class PresenterA: PresenterProtocol {
weak var view: ViewController<PresenterA>!
var onDidPressCallback: ((String) -> Void)?
required init() { ... }
}
class PresenterB: PresenterProtocol {
weak var view: ViewController<PresenterB>!
var onDidPressCallback: ((Apple) -> Void)?
required init() { ... }
}
class ViewController<Presenter: PresenterProtocol>: UIViewController {
var presenter = Presenter()
override func viewDidLoad() {
super.viewDidLoad()
presenter.onDidPressCallback = { value in
// show the value in the UI
}
...
}
}
Hopefully, that answers the question, but there are a few issues here:
Presenters should be independent of UIKit. You do not want to have a reference to your view controller (or have any UIKit dependencies) in the presenter. Only the “view” (views, view controllers, etc.) should have UIKit dependencies. This separation of responsibilities is a core concept of MVP.
There are a number of options to allow the presenter to inform the view of events (such as delegate protocols, async-await, etc.). But you have a closure variable, and that is a perfectly adequate way to proceed. The presenter does not need a reference to the view controller, but rather should simply call the appropriate closure. The view controller, obviously, will just set those closure variables so it can respond to the presenter events.
But, as I said, there are a variety of different approaches to inform the “view” of events triggered by the presenter. But the presenter should not be reaching into the view controller, itself. No reference to the view controller is required or desired.
By making the view controller a generic, it can no longer be instantiated via standard UIKit mechanisms (storyboards, NIBs, etc.). Unless you really want to lose the benefits of IB outlets, actions, etc., and do everything programmatically, you probably do not want to make it generic. You introduce significant development/maintenance costs for the sake of generics.
Besides, a view controller for Apple and a view controller for String will probably have unique controls. Making it a generic might feel intuitively appealing at first, but in practice, it falls apart over time. The few times I went down this road, I ended up regretting it. As the UI evolves, you end up refining/specializing the UI for the individual types over time. The few times I tried permutations of this pattern, I found myself ripping it out later. It feels so intuitive, but it often becomes a hindrance later.
Fortunately, when you remove the reference to the view controller from the presenter and do not make the view controller generic, much of this noise disappears:
protocol PresenterProtocol {
associatedtype ResultType
var onDidPressCallback: ((ResultType) -> Void)? { get set }
}
class PresenterA: PresenterProtocol {
var onDidPressCallback: ((String) -> Void)?
// ...
}
class PresenterB: PresenterProtocol {
var onDidPressCallback: ((Apple) -> Void)?
// ...
}
class ViewControllerA: UIViewController {
var presenter = PresenterA()
override func viewDidLoad() {
super.viewDidLoad()
presenter.onDidPressCallback = { value in
// show the data in the UI
}
// ...
}
}
At this point, the protocol becomes more of a contract to ensure that all the presenters follow certain conventions for the shared functionality. But we no longer tie ourselves down, limiting our presenters to least-common-denominator functionality.

Correct way to add Generic types inheritance

I have created these classes:
class BaseViewModel<NavigatorType> {
typealias Navigator = NavigatorType
var navigator: Navigator!
}
class BaseViewController<ViewModel: BaseViewModel<Any>>: UIViewController {
typealias ViewModel = ViewModel
var viewModel: ViewModel!
}
class MyVC: BaseViewController<MyViewModel> {
}
class MyViewModel: BaseViewModel<MyNavigator> {
}
Now the problem is I receive this error on MyVC class:
'BaseViewController' requires that 'MyViewModel' inherit from
'BaseViewModel<Any>'
If I remove BaseViewModel<Any> from my BaseViewController generic parameter then the error goes. But I want to restrict view controller's generic ViewModel to inherit from BaseViewModel only.
Any idea how to do that?
The specialized type BaseViewModel<MyNavigator> is different from the specialized type BaseViewModel<Any>. I suspect you're picturing it like BaseViewModel<MyNavigator> can override/inherit from BaseViewModel<Any> since MyNavigator is a more specific Any. But Swift doesn't work that way. I don't know enough about the internals of Swift to know why it doesn't, but it doesn't!
Perhaps try making BaseViewModel a protocol instead:
protocol BaseViewModel {
associatedtype Navigator
var navigator: Navigator! { get }
}
class BaseViewController<ViewModel: BaseViewModel>: UIViewController {
var viewModel: ViewModel!
}
class MyVC: BaseViewController<MyViewModel> {
func foo() {
viewModel.navigator.bar()
print(viewModel.mySpecificProperty)
}
}
class MyViewModel: BaseViewModel {
var navigator: MyNavigator!
var mySpecificProperty: String = "Hello!"
}
class MyNavigator {
func bar() {
print("MyNavigator bar")
}
}
If the goal is to give view controllers a specific typed view model to play with, that should do it.

Using protocol's associated type in generic functions

I'm trying to write a simple MVP pattern to follow in my app, so I've written two porotocols to define View Controller and Presenters:
protocol PresenterType: class {
associatedtype ViewController: ViewControllerType
var viewController: ViewController? { get set }
func bind(viewController: ViewController?)
}
protocol ViewControllerType: class {
associatedtype Presenter: PresenterType
var presenter: Presenter { get }
init(presenter: Presenter)
}
After having those defined I started writing some RootViewController and RootViewPresenter. The latter looks like:
protocol RootViewControllerType: ViewControllerType {
}
final class RootPresenter<VC: RootViewControllerType>: PresenterType {
weak var viewController: VC?
func bind(viewController: VC?) {
self.viewController = viewController
}
}
Up to this point everything complies and is fine, but when I start implementing View Controller like this:
protocol RootPresenterType: PresenterType {
}
final class RootViewController<P: RootPresenterType>: UIViewController, ViewControllerType {
let presenter: P
init(presenter: Presenter) {
self.presenter = presenter
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
presenter.bind(viewController: self)
}
}
Immediately I get the following error message:
Cannot convert value of type 'RootViewController' to expected argument type '_?'
I know that protocols with associated types can introduce some limitations, but this example is pretty straightforward and I can't make it work. Is it possible to achieve something that I want, or do I have to look for some other, less Swifty pattern?
I don't think what you're trying to achieve is possible due to the circular dependency between the respective associated types of the PresenterType and ViewControllerType protocols.
Consider for a moment if the suspect code above did compile ... how would you go about instantiating either the RootPresenter or RootViewController classes? Because both depend on one another, you'll end up with errors like the following:
As you can see, the compiler can't fully resolve the generic parameters due to the associated types.
I think your best bet is to remove the associated type from one or both of the protocols. For example, removing the associated type from the PresenterType protocol and updating the RootPresenter class breaks the circular dependency and allows your code to compile normally.
protocol PresenterType: class {
var viewController: UIViewController? { get set }
func bind(viewController: UIViewController?)
}
final class RootPresenter: PresenterType {
weak var viewController: UIViewController?
func bind(viewController: UIViewController?) {
self.viewController = viewController
}
}

Swift - Can I achieve mutual generics reference to classes?

I want to have two classes that are bound. This means, both classes know about each other methods.
BaseController <> BaseView
Those classes are used to declare subclasses, for example
LoginController <> LoginView.
BaseController has a strong reference to its view of kind <T : BaseView>, BaseView should have a weak reference to its controller of kind <T: BaseController>
I have managed to have a generic to BaseView:
class BaseController <T: BaseScreen>
so that I do:
class LoginController : BaseController<LoginView>
that works and the controller has direct access (visibility) to the view functions and I don't need to cast every time.
However I cannot do the same on the View, because if I do
class BaseView <T : BaseController>
Then the compiler forces me to do
class BaseView <T : BaseController <BaseScreen>>
Which results in a recursion error.
Is there a way I can do that cross reference using generics? Right now I'm sticking with the view simply casting to the Controller in case it is needed.
You can use protocols and associated types instead of base classes (and implement the methods of the base type in a protocol extension):
protocol ControllerProtocol: class {
associatedtype View: ViewProtocol
var view: View { get }
}
protocol ViewProtocol: class {
associatedtype Controller: ControllerProtocol
weak var controller: Controller { get }
}
A class implementing one of the protocols has to specify a typealias to the desired related class:
class LoginController: ControllerProtocol {
typealias View = LoginView
var view: LoginView
...
}
class LoginView: ViewProtocol {
typealias Controller = LoginController
weak var controller: LoginController
...
}

Swift protocol nested in a class

I would like to nest a protocol in my class to implement the delegate pattern like so :
class MyViewController : UIViewController {
protocol Delegate {
func eventHappened()
}
var delegate:MyViewController.Delegate?
private func myFunc() {
delegate?.eventHappened()
}
}
But the compiler will not allow it :
Protocol 'Delegate' cannot be nested inside another declaration
I can easily make it work by declaring MyViewControllerDelegate outside of the class scope.
My question is why such a limitation ?
according to the swift documenation
Swift enables you to define nested types, whereby you nest supporting enumerations, classes, and structures within the definition of the type they support.
Given protocols are not on that list, it doesn't appear that it's currently supported.
It's possible they will add the feature at some point, (Swift was announced less than 2 years go after all). Any idea on why they won't or haven't would be speculation on my part.
this is my work around:
protocol MyViewControllerDelegate : class {
func eventHappened()
}
class MyViewController : UIViewController {
typealias Delegate = MyViewControllerDelegate
weak var delegate: Delegate?
private func myFunc() {
delegate?.eventHappened()
}
}
A separate problem with your class is that delegate does not have a concrete type. You can get away with declaring it a MyViewController.Delegate? because it is an optional type and can be .None. But that just makes your private myFunc dead code. Only enumerations, classes, and structures can conform to a protocol. Which of those three types is delegate?
That said, this is not the cause of your problem. When I make a real type for delegate and make it conform to the Delegate protocol, I still get the same error.
class MyViewController: UIViewController {
protocol Delegate {
func eventHappened()
}
class Classy: Delegate {
func eventHappened() {
print("eventHappened")
}
}
var delegate: Classy? = Classy()
func myFunc() {
delegate?.eventHappened()
}
}
As an esoteric exercise this might be interesting in pushing the bounds of what a class might do but no one should every try to declare a protocol inside of a class. Protocols are all about type composition and collections. There is no code reuse scenario when you are limited to being in the same outer class.