NikGreen
NikGreen

Reputation: 700

Use protocol with constrained associated type as property in 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 {
 ....
}

Upvotes: 4

Views: 3230

Answers (3)

Rob Napier
Rob Napier

Reputation: 299275

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>()

Upvotes: 1

sj-r
sj-r

Reputation: 571

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.

Upvotes: -1

regina_fallangi
regina_fallangi

Reputation: 2198

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.

Upvotes: 4

Related Questions