Procotol composition on the same property - swift

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.

Related

closure through typealias swift not working

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

Swift protocol inside of protocol

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.

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.

How to present PublishSubject as Observable in MVVM?

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

Ambiguous functions in multiple protocol extensions?

I have multiple protocols that have the same function name. Some protocols have associated types, where I can't figure out how to call the functions as I do in non-generic protocols. I get the error: Protocol 'MyProtocol1' can only be used as a generic contraint because it has Self or associated type requirements
Here's what I'm trying to do:
protocol Serviceable {
associatedtype DataType
func get(handler: ([DataType] -> Void)?)
}
struct PostService: Serviceable {
func get(handler: ([String] -> Void)? = nil) {
print("Do something...")
}
}
protocol MyProtocol1: class {
associatedtype ServiceType: Serviceable
var service: ServiceType { get }
}
extension MyProtocol1 {
func didLoad(delegate: Self) {
print("MyProtocol1.didLoad()")
}
}
protocol MyProtocol2: class {
}
extension MyProtocol2 {
func didLoad(delegate: MyProtocol2) {
print("MyProtocol2.didLoad()")
}
}
class MyViewController: UIViewController, MyProtocol1, MyProtocol2 {
let service = PostService()
override func viewDidLoad() {
super.viewDidLoad()
didLoad(self as MyProtocol1) // Error here: Protocol 'MyProtocol1' can only be used as a generic contraint because it has Self or associated type requirements
didLoad(self as MyProtocol2)
}
}
How can I specifically call the function from a generic protocol extension?
It's simple to achieve by turning the protocol into a generic (see below), or by creating a type eraser for these protocols, but this very strongly suggests that you have a design problem and you should redesign your classes and/or extensions. A collision like this suggests strongly that MyStruct is doing too many things itself because it's being pulled in multiple directions by MyProtocol1 and MyProtocol2. There should likely be two objects here instead. (Composition rather than inheritance.)
class MyStruct: MyProtocol1, MyProtocol2 {
let service = PostService()
func prot1Load<T: MyProtocol1>(t: T) {
t.didLoad()
}
func prot2Load<T: MyProtocol2>(t: T) {
t.didLoad()
}
init() {
prot1Load(self)
prot2Load(self)
}
}
To your particular example in the comments, I would use composition rather than inheritance. You're treating protocols like multiple-inheritance, which is almost never right. Instead compose out of things that conform to a protocol.
protocol LoadProviding {
func load()
}
struct MyLoader1: LoadProviding {
func load() {
print("MyLoader1.didLoad()")
}
}
struct MyLoader2: LoadProviding {
func load() {
print("MyLoader2.didLoad()")
}
}
protocol Loader {
var loaders: [LoadProviding] { get }
}
extension Loader {
func loadAll() {
for loader in loaders {
loader.load()
}
}
}
class MyStruct: Loader {
let service = PostService()
let loaders: [LoadProviding] = [MyLoader1(), MyLoader2()]
init() {
loadAll()
}
}
Of course you don't really have to have LoadProviding be a full struct. It could just be a function if that's all you need:
typealias LoadProviding = () -> Void
func myLoader1() {
print("MyLoader1.didLoad()")
}
func myLoader2() {
print("MyLoader2.didLoad()")
}
protocol Loader {
var loaders: [LoadProviding] { get }
}
extension Loader {
func loadAll() {
for loader in loaders {
loader()
}
}
}
class MyStruct: Loader {
let service = PostService()
let loaders: [LoadProviding] = [myLoader1, myLoader2]
init() {
loadAll()
}
}
If you have time to wade through a video on the subject, you may be interested in the Beyond Crusty: Real World Protocols talk from dotSwift. It's about this and similar problems.