Use protocol with constrained associated type as property in Swift - swift

I'm trying to implement a datasource protocol with associated type
protocol DataSourceCompatible {
associatedtype CellModel
func cellModelForItem(at indexPath: IndexPath) -> CellModel
}
Protocol AddressBookViewModelType inherits from base protocol and constrains the associated value to another protocol
protocol AddressBookViewModelType: class, DataSourceCompatible where CellModel == AddressBookCellModelType {
}
AddressBookViewModel is a concrete implementation of AddressBookViewModelType protocol
class AddressBookViewModel: AddressBookViewModelType {
func cellModelForItem(at indexPath: IndexPath) -> AddressBookCellModelType {
let contact = sectionedContacts[indexPath.section][indexPath.row]
return AddressBookCellModel(contact: contact)
}
}
The code compiles fine, however when I declare the viewmodel as a property on my viewcontroller, the compiler fails with Protocol 'AddressBookViewModelType' can only be used as a generic constraint because it has Self or associated type requirements.
class AddressBookViewController: UIViewController {
private var viewModel: AddressBookViewModelType!
func configure(viewModel: AddressBookViewModelType) {
self.viewModel = viewModel
}
...
}
I remember seeing type erasure might solve the issue, but I'm not that familiar with type erasure concept. Is there a way to solve the issue?
Update:
How are AddressBookCellModelType and AddressBookCellModel related here?
It's a struct implementing a protocol.
protocol AddressBookCellModelType {
var name: String { get }
var photo: UIImage? { get }
var isInvited: Bool { get }
}
struct AddressBookCellModel: AddressBookCellModelType {
....
}

Have you tried just using it as a generic, like the warning/error says:
class AddressBookViewController<T: AddressBookViewModelType> : UIViewController {
private var viewModel: T!
func configure(viewModel: T) {
self.viewModel = viewModel
}
...
}
You'd need to initialise your controller with a property of variable T so the type can be inferred.

To expand on my questions in the comments, looking at this code it looks like it would be exactly as flexible without adding AddressBookCellModelType or AddressBookViewModelType, and this would also get rid of the headaches, while still being generic over DataSourceCompatible.
// This protocol is fine and very useful for making reusable view controllers. Love it.
protocol DataSourceCompatible {
associatedtype CellModel
func cellModelForItem(at indexPath: IndexPath) -> CellModel
}
// No need for a protocol here. The struct is its own interface.
// This ensures value semantics, which were being lost behind the protocol
// (since a protocol does not promise value semantics)
struct AddressBookCellModel {
var name: String
var photo: UIImage?
var isInvited: Bool
}
// AddressBookViewModel conforms to DataSourceCompatible
// Its conformance sets CellModel to AddressBookCellModel without needing an extra protocol
class AddressBookViewModel: DataSourceCompatible {
let sectionedContacts: [[AddressBookCellModel]] = []
func cellModelForItem(at indexPath: IndexPath) -> AddressBookCellModel {
return sectionedContacts[indexPath.section][indexPath.row]
}
}
class AddressBookViewController: UIViewController {
private var viewModel: AddressBookViewModel!
func configure(viewModel: AddressBookViewModel) {
self.viewModel = viewModel
}
}
Doing it this way allows for a generic VC without introducing more pieces that required:
class DataSourceViewController<DataSource: DataSourceCompatible>: UIView {
private var viewModel: DataSource.CellModel!
func configure(viewModel: DataSource.CellModel) {
self.viewModel = viewModel
}
}
let vc = DataSourceViewController<AddressBookViewModel>()

It's just Swift specification that you cannot use 'protocol with associated type' as a type declaration. The reason is that the compiler can't know what the associated type will actually be at the compile time, which violates the "type-safety" of Swift.
The solution would be to use type-eraser like you said, or make the type generic.

Related

compiler "has no member" error with MVVM design pattern 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.

how do i call a protocol in one class from another in swift?

I have my viewcontroller that implements one Protocol,
import UIKit
class FirstScreenViewController: UIViewController, mainViewProtocol {
var presenter: mainPresenterProtocol?
//Protocol Functions
static func showSmallHeadline(textToShow: String) {
<#code#>
}
func showHeadline(textToShow: String) {
<#code#>
}
}
I have my presenter that implement second Protocol
import Foundation
class MainPresenter: mainPresenterProtocol {
var screenViewController: mainViewProtocol?
//confirms protocol
static func presenterProtocolFuncOne() {
<#code#>
}
func presenterProtocolFuncTwo(numOne: Int, numTwo: Int, sucssesMessage: String, failMessage: String) -> String {
<#code#>
}
func presenterProtocolFucThree() -> Bool {
<#code#>
}
}
how do I call the functions in my presenter (that implements them through the protocol) from my viewcontroller,
and how do I call the functions in my viewcontroller (that implements them through the protocol) from my presenter ?
Thank you !
The key thing here - strong relationship between controller and presenter. You should avoid that by adding weak to presenter property in controller.
protocol MainViewProtocol {
func controllerProtocolFunc()
}
protocol MainPresenterProtocol: class {
func presenterProtocolFunc()
}
class FirstScreenViewController: UIViewController, MainViewProtocol {
weak var presenter: MainPresenterProtocol?
func controllerProtocolFunc() { }
}
class MainPresenter: MainPresenterProtocol {
var screenViewController: MainViewProtocol?
func presenterProtocolFunc() { }
}
Next step, when you need to call presenter(controller) func from controller(presenter), simply call it with optional chaining:
presenter?.presenterProtocolFunc()
// or
screenViewController?.controllerProtocolFunc()
P.S. Also, note from docs:
Because protocols are types, begin their names with a capital letter
(such as FullyNamed and RandomNumberGenerator) to match the names of
other types in Swift (such as Int, String, and Double).
Add properties to each class referencing the other object by the type of the protocol.
class FirstScreenViewController: UIViewController, mainViewProtocol {
var presenter: mainPresenterProtocol?
// rest of the code
}
class MainPresenter: mainPresenterProtocol {
var screenViewController: mainViewProtocol?
// rest of the code
}
Assign the properties after constructing both objects. The ? after the protocol type makes them optional, allowing you delay the assignment to the point in time when both objects exist. Alternatively, you can swap out the ? for a ! if you are certain the properties will be assigned non-nil values, so you can save a lot of nil-checking and dereferencing.
let viewController = FirstScreenViewController()
let mainPresenter = MainPresenter()
viewController.presenter = mainPresenter
presenter.screenViewController = viewController

Swift generics, cannot infer type T

I've tried a bunch of different things but i'm not good at generics.
How do I call a function with a variable that needs to be both a solid type like UIViewController, as well as conforming to a protocol like NavBarResponder ?
Thanks!
weak var lastNavBarResponder: UIViewController?
func reloadExtendedNavBar() {
self.updateState()
self.updateStatusBadges()
if let vc = self.lastNavBarResponder {
self.setup(viewController: vc) // Error: Generic parameter T cannot be inferred
}
}
func setup<T: UIViewController>(viewController: T) where T: NavBarResponder {
...
I suspect the error is because UIViewController doesn't naturally conform to NavBarResponder, so the compiler can't guarantee that lastNavBarResponder fits the requirements of setup.
Try changing lastNavBarResponder to be of a type that is a UIViewController that conforms to the NavBarResponder protocol.
Here is a self-contained example showing how this would be implemented.
protocol NavBarResponder {}
class NavBarResponderController: UIViewController {}
extension NavBarResponderController: NavBarResponder {}
var lastNavBarResponder: NavBarResponderController? = NavBarResponderController()
func setup<T: UIViewController>(viewController: T) where T: NavBarResponder {
print("Works")
}
func reloadExtendedNavBar() {
if let vc = lastNavBarResponder {
setup(viewController: vc)
}
}
reloadExtendedNavBar()

Swift delegate for a generic class

I have a class that needs to call out to a delegate when one of its properties changes. Here are the simplified class and protocol for the delegate:
protocol MyClassDelegate: class {
func valueChanged(myClass: MyClass)
}
class MyClass {
weak var delegate: MyClassDelegate?
var currentValue: Int {
didSet {
if let actualDelegate = delegate {
actualDelegate.valueChanged(self)
}
}
}
init(initialValue: Int) {
currentValue = initialValue
}
}
This all works just fine. But, I want to make this class generic. So, I tried this:
protocol MyClassDelegate: class {
func valueChanged(genericClass: MyClass)
}
class MyClass<T> {
weak var delegate: MyClassDelegate?
var currentValue: T {
didSet {
if let actualDelegate = delegate {
actualDelegate.valueChanged(self)
}
}
}
init(initialValue: T) {
currentValue = initialValue
}
}
This throws two compiler errors. First, the line declaring valueChanged in the protocol gives: Reference to generic type 'MyClass' requires arguments in <...>. Second, the call to valueChanged in the didSet watcher throws: 'MyClassDelegate' does not have a member named 'valueChanged'.
I thought using a typealias would solve the problem:
protocol MyClassDelegate: class {
typealias MyClassValueType
func valueChanged(genericClass: MyClass<MyClassValueType>)
}
class MyClass<T> {
weak var delegate: MyClassDelegate?
var currentValue: T {
didSet {
if let actualDelegate = delegate {
actualDelegate.valueChanged(self)
}
}
}
init(initialValue: T) {
currentValue = initialValue
}
}
I seem to be on the right path, but I still have two compiler errors. The second error from above remains, as well as a new one on the line declaring the delegate property of MyClass: Protocol 'MyClassDelegate' can only be used as a generic constraint because it has Self or associated type requirements.
Is there any way to accomplish this?
It is hard to know what the best solution is to your problem without having more information, but one possible solution is to change your protocol declaration to this:
protocol MyClassDelegate: class {
func valueChanged<T>(genericClass: MyClass<T>)
}
That removes the need for a typealias in the protocol and should resolve the error messages that you've been getting.
Part of the reason why I'm not sure if this is the best solution for you is because I don't know how or where the valueChanged function is called, and so I don't know if it is practical to add a generic parameter to that function. If this solution doesn't work, post a comment.
You can use templates methods with type erasure...
protocol HeavyDelegate : class {
func heavy<P, R>(heavy: Heavy<P, R>, shouldReturn: P) -> R
}
class Heavy<P, R> {
typealias Param = P
typealias Return = R
weak var delegate : HeavyDelegate?
func inject(p : P) -> R? {
if delegate != nil {
return delegate?.heavy(self, shouldReturn: p)
}
return nil
}
func callMe(r : Return) {
}
}
class Delegate : HeavyDelegate {
typealias H = Heavy<(Int, String), String>
func heavy<P, R>(heavy: Heavy<P, R>, shouldReturn: P) -> R {
let h = heavy as! H // Compile gives warning but still works!
h.callMe("Hello")
print("Invoked")
return "Hello" as! R
}
}
let heavy = Heavy<(Int, String), String>()
let delegate = Delegate()
heavy.delegate = delegate
heavy.inject((5, "alive"))
Protocols can have type requirements but cannot be generic; and protocols with type requirements can be used as generic constraints, but they cannot be used to type values. Because of this, you won't be able to reference your protocol type from your generic class if you go this path.
If your delegation protocol is very simple (like one or two methods), you can accept closures instead of a protocol object:
class MyClass<T> {
var valueChanged: (MyClass<T>) -> Void
}
class Delegate {
func valueChanged(obj: MyClass<Int>) {
print("object changed")
}
}
let d = Delegate()
let x = MyClass<Int>()
x.valueChanged = d.valueChanged
You can extend the concept to a struct holding a bunch of closures:
class MyClass<T> {
var delegate: PseudoProtocol<T>
}
struct PseudoProtocol<T> {
var valueWillChange: (MyClass<T>) -> Bool
var valueDidChange: (MyClass<T>) -> Void
}
Be extra careful with memory management, though, because blocks have a strong reference to the object that they refer to. In contrast, delegates are typically weak references to avoid cycles.

Swift protocol with associated type: how to use in an abstract method?

I have two protocols, one for a ViewModel and one for a ConfigurableView that takes the ViewModel type as an associated type.:
public protocol ViewModel {}
public protocol ConfigurableView {
associatedtype ViewModelType: ViewModel
func configure(with viewModel: ViewModelType)
}
In my method that configures an abstract view with an abstract model:
let viewModel = getMyViewModel() // returns ViewModel
if let configurableView = cell as? ConfigurableView {
configurableView.configure(with: viewModel)
}
I get "Protocol 'ConfigurableView' can only be used as a generic constraint because it has Self or associated type requirements".
How do I tell the compiler that I want to configure the view with whatever associated type this instance has, if it's a ConfigurableView instance?
I actually found what I think is a decent solution that didn't require too much mangling of my architecture. Thanks to #lib for putting me on the right path. The trick was to have a protocol above that doesn't have an associatedType requirement with an extension that casts the generic ViewModel to the associatedType of the specific one. I believe this is type erasure? But it doesn't look like any of the examples I read.
public protocol ViewModel {}
/*
This parent protocol exists so callers can call configure on
a ConfigurableView they don't know the specific type of.
*/
public protocol AnyConfigurableView {
func configure(with anyViewModel: ViewModel)
}
public protocol ConfigurableView: AnyConfigurableView {
associatedtype ViewModelType: ViewModel
func configure(with viewModel: ViewModelType)
}
/*
This extension does the trick of converting from the generic
form of ConfigurableView to the specific form.
*/
public extension ConfigurableView {
func configure(with anyViewModel: ViewModel) {
guard let viewModel = anyViewModel as? ViewModelType else {
return
}
configure(with: viewModel)
}
}
Usage:
let viewModel = getViewModel()
(someView as? AnyConfigurableView)?.configure(with: viewModel)
You cannot use Generic Protocols other way than type constraints. Without generic type defined, compiler cannot compare type conformity. If I understood you correctly, then you need to define generic CellConfigurator class. One of possible solutions below:
1. Cell and configurator abstractions
protocol ConfigurableCell {
associatedtype DataType
func configure(viewModel: DataType?)
}
protocol CollectionViewCellConfigurator {
static var reuseId: String { get }
func configure(cell: UICollectionViewCell)
var item: UniqueIdentifiable? { get }
}
final class CellConfigurator<CellType: ConfigurableCell, DataType>: CollectionViewCellConfigurator where CellType.DataType == DataType, CellType: UICollectionViewCell {
/// Cell Reuse identifier
static var reuseId: String { return CellType.reuseId }
/// Configures cell and populates it with `viewModel`
///
/// - Parameter cell: Cell to configure
func configure(cell: UICollectionViewCell) {
(cell as! CellType).configure(viewModel: item as? DataType)
}
/// Initializer
///
/// - Parameter item: Data item (usually ViewModel of the cell)
init(item: DataType?) {
self.item = item
}
}
2. Usage
Your data source will now operate with CellConfigurators looking like CellConfigurator<CellType /*UI(CollectionView/TableView)Cell subclass*/, CellData /*Data you need to populate to the cell*/>(item: cellData)
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let configItem = yourDataSource.rows[indexPath.row]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: type(of: configItem).reuseId, for: indexPath)
configItem.configure(cell: cell)
return cell
}
Hope it helps. Good luck