compiler "has no member" error with MVVM design pattern Swift - swift

Im trying to write boiler plate code for an MVVM architecture and I'm trying to make the View interface inside my ViewModel instead of binding properties between them.
I do get a compiler error saying Type 'V.Command' has no member 'reload' How can I improve my code so that View is abstracted inside my ViewModel?
Interfaces
protocol Actionable {
associatedtype Command
func perform(command: Command)
}
extension Actionable {
func perform(command: Command) {}
}
protocol Rendering {
associatedtype Model
func render(with model: Model)
}
extension Rendering {
func render(with dependencies: Model) {}
}
protocol VMView: Rendering, Actionable {}
protocol ViewModel {
associatedtype Command
associatedtype Dependencies
associatedtype Model
}
View Implementation
final class NewViewVC: UIViewController, VMView {
typealias Model = NewViewVM<NewViewVC>.Model
typealias Command = NewViewVM<NewViewVC>.Command
func perform(command: Command) {
print("performing \(command)")
}
func render(with model: Model) {
print("rendering \(model)")
}
var viewModel: NewViewVM<NewViewVC>!
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension NewViewVC {
static func create(dependencies: NewViewVM<NewViewVC>.Dependencies) -> NewViewVC {
let vc = NewViewVC()
let viewModel = NewViewVM.create(dependencies: dependencies, services: .init(), view: vc)
vc.viewModel = viewModel
return vc
}
static func createMock(dependencies: NewViewVM<NewViewVC>.Dependencies, services: NewViewVM<NewViewVC>.Dependencies.Services) -> NewViewVC {
let vc = NewViewVC()
let viewModel = NewViewVM.create(dependencies: dependencies, services: services, view: vc)
vc.viewModel = viewModel
return vc
}
}
ViewModel Implementation
protocol NewViewVMInterface {
func reloadData()
}
final class NewViewVM<V: VMView>: ViewModel, NewViewVMInterface {
struct Model {}
enum Command {
case start, reload
}
struct Dependencies {
struct Services {
}
}
private var view: V!
private var dependencies: Dependencies!
private var services: Dependencies.Services!
static func create(dependencies: Dependencies, services: Dependencies.Services, view: V) -> NewViewVM
where V.Model == Model, V.Command == Command {
let viewModel = NewViewVM()
viewModel.dependencies = dependencies
viewModel.services = services
viewModel.view = view
return viewModel
}
func reloadData() {
// fetch data
view.perform(command: .reload) // <- Where error happens
}
}
Thanks for any help. A quick fix I can find is to force cast the command to V.Command but that's very ugly. I feel there is a better solution.

Looks like if I put the NewViewVMInterface conformance to an extension I'll be able to do something like this:
extension NewViewVM: NewViewVMInterface where V.Command == Command, V.Model == Model {
func reloadData() {
view.perform(command: .reload)
}
}
However in my past experiences I've noticed sometime when making SDKs having protocol conformances in extensions results in unexpected behaviours. Im still open to any other suggestions out there.

Related

Value of protocol type 'InheritingProtocol: Protocol' cannot conform to 'Protocol'

I have the below code which aims an abstraction -without being have to casting Decodables - for DataModels across the app. I wanted use these DataModels to centrelize them. This is how I far I came right now and I am kind of in dead end.
In this configuration, the code tells me that ProfileResponseDelegate cannot conform to ModelDelegate when ProfileResponseDelegate is a protocol, which makes sense.
protocol ModelDelegate: class {
associatedtype DataType: Decodable
func didReceive(data: DataType)
}
class Model<Type, Delegate: ModelDelegate> where Type == Delegate.DataType {
var data: Type?
weak var delegate: Delegate?
func requestData() { return }
}
protocol ProfileResponseDelegate: ModelDelegate where DataType == ProfileResponse {}
//throws Value of protocol type 'ProfileResponseDelegate' cannot conform to 'ModelDelegate'; only struct/enum/class types can conform to protocols
class ProfileResponseModel: Model<ProfileResponse, ProfileResponseDelegate> {
override func requestData() {
guard let data = data else {
// go to api to get data
return
}
delegate?.didReceive(data: data)
}
}
class Controller: UIViewController, ProfileResponseDelegate {
let model = ProfileResponseModel()
override func viewDidLoad() {
super.viewDidLoad()
model.delegate = self
model.requestData()
}
func didReceive(data: ProfileResponse) {
//tell view code to update regarding data
}
}
When I change ProfileResponseDelegate to be a class -without not being a delegate anymore, but anyway- the code does not allow Controller to inherit from both UIViewController and ProfileResponseDelegate reasoning a class cannot inherit from multiple classes. which again makes sense.
class ProfileResponseDelegate: ModelDelegate {
typealias DataType = ProfileResponse
func didReceive(data: ProfileResponse) {
return
}
}
class Controller: UIViewController, ProfileResponseDelegate {
let model = ProfileResponseModel()
override func viewDidLoad() {
super.viewDidLoad()
model.delegate = self
model.requestData()
}
override func didReceive(data: ProfileResponse) {
//tell view code to update regarding data
}
}
With respect to first configuration, I could not make it work. However for the second one, when Controller just inherits from ProfileResponseDelegate it works just fine.
I have to find a way to make this work -preferably the first configuration- and need your advise. Much appreciated in advance.
UPDATE
So I have removed the associatedType from the ModelDelegate and removed ProfileResponseModel. Right now code looks like this.
protocol ModelDelegate: class {
//associatedtype DataType: Decodable
func didReceive<T: Decodable>(data: T)
}
class Model<Type: Decodable> {
var data: Type?
weak var delegate: ModelDelegate?
func requestData() { return }
}
//protocol ProfileResponseDelegate: ModelDelegate where DataType == ProfileResponse {}
class ProfileResponseModel: Model<ProfileResponse> {
override func requestData() {
guard let data = data else {
// go to api to get data
return
}
delegate?.didReceive(data: data)
}
}
class Controller: UIViewController, ModelDelegate {
let model = ProfileResponseModel()
override func viewDidLoad() {
super.viewDidLoad()
model.delegate = self
model.requestData()
}
func didReceive<T>(data: T) where T : Decodable {
//I want this `data` to come as what it is.
if let response = data as? ProfileResponse {
print(type(of: response))
}
}
}
It works likes this, however my ultimate purpose for doing this to not being have to cast the data to ProfileResponse here -and in other places to other Decodable type-.

Core Data and SwiftUI polymorphism issues

Having troubles putting down together SwiftUI and generic types for handling Core Data.
Consider following example:
Parent is abstract. Foo and Bar are children of Parent and they have some custom attributes.
Now what I want to do, is roughly that:
protocol EntityWithView {
associatedtype T: View
func buildView() -> T
}
extension Parent: EntityWithView {
func buildView() -> some View {
fatalError("Re-implement in child")
}
}
extension Foo {
override func buildView() -> some View {
return Text(footribute)
}
}
extension Bar {
override func buildView() -> some View {
return Text(atrribar)
}
}
struct ViewThatUsesCoreDataAsModel: View {
let entities: [Parent]
var body: some View {
ForEach(entities) { entity in
entity.buildView()
}
}
}
I would want to add polymorphic builder to my core data entities that shape data or build views, that confirm to common interface so I can use them without casting/typing.
Problem that compiler throws errors if I try to modify generated Core data entity directly not through extension, and confirming to protocol though extension doesn't allow overriding.
Ok, this is head-breaking (at least for Preview, which gone crazy), but it works in run-time. Tested with Xcode 11.4 / iOS 13.4.
As we need to do all in extension the idea is to use dispatching via Obj-C messaging, the one actually available pass to override implementation under such requirements.
Note: use Simulator or Device
Complete test module
protocol EntityWithView {
associatedtype T: View
var buildView: T { get }
}
extension Parent {
// allows to use Objective-C run-time messaging by complete
// type erasing.
// By convention subclasses
#objc func generateView() -> Any {
AnyView(EmptyView()) // << safe
//fatalError("stub in base") // << alternate
}
}
extension Parent: EntityWithView {
var buildView: some View {
// restory SwiftUI view type from dispatched message
guard let view = self.generateView() as? AnyView else {
fatalError("Dev error - subview must generate AnyView")
}
return view
}
}
extension Foo {
#objc override func generateView() -> Any {
AnyView(Text(footribute ?? ""))
}
}
extension Bar {
#objc override func generateView() -> Any {
AnyView(Text(attribar ?? ""))
}
}
struct ViewThatUsesCoreDataAsModel: View {
let entities: [Parent]
var body: some View {
VStack {
ForEach(entities, id: \.self) { entity in
entity.buildView
}
}
}
}
struct DemoGeneratingViewInCoreDataExtension: View {
#Environment(\.managedObjectContext) var context
var body: some View {
ViewThatUsesCoreDataAsModel(entities: [
Foo(context: context),
Bar(context: context)
])
}
}

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

Procotol composition on the same property

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.

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