user3746428
user3746428

Reputation: 11175

Nested enums which conform to Equatable protocol produce error

I have a protocol named TableViewItem. This protocol enforces that conforming objects implement a type property, which has the protocol TableViewCellIdentifiable as its type. TableViewCellIdentifiable is used to group three nested enums together, as shown below:

internal protocol TableViewCellIdentifiable: Equatable { }

internal enum TableViewCellType {
    internal enum PortfolioSelection: String, TableViewCellIdentifiable {
        case portfolio = "portfolioTableViewCell"
        case enterPortfolioDetails = "enterPortfolioDetailsTableViewCell"
        case addPortfolio = "actionTableViewCell"
    }

    internal enum EditPortfolio: String, TableViewCellIdentifiable {
        case editPortfolioName = "editPortfolioNameTableViewCell"
        case deletePortfolio = "deletePortfolioTableViewCell"
    }

    internal enum Portfolio: String, TableViewCellIdentifiable {   
        case portfolioAsset = "portfolioAssetTableViewCell"
        case addAsset = "actionTableViewCell"
    }
}

Here is an example of how this is being used:

internal final class EditPortfolioNameTableViewItem: TableViewItem {

    // MARK: - Internal Properties

    internal let type: TableViewCellIdentifiable = TableViewCellType.EditPortfolio.editPortfolioName
    internal let viewModel: TableViewCellModel

    // MARK: - Initialization

    internal init(viewModel: EditPortfolioNameTableViewCellModel) {
        self.viewModel = viewModel
    }
}

Unfortunately, on the line that I am declaring the type property, I receive the following error:

Protocol 'TableViewCellIdentifiable' can only be used as a generic constraint because it has Self or associated type requirements

I have read through other questions/answers from others who have encountered this error but I can't quite understand why this particular implementation is problematic, and what the solution would be. I know that Equatable is the source of the problem, however this is crucial to the functionality, as the enums serve two purposes:

  1. To provide reuse identifiers for the table view cells (the raw values).
  2. To allow types to be compared - i.e:

    self.tableViewItems.contains(where: { $0.type == item.type })
    

Any suggestions would be much appreciated, even if it means taking an alternative approach.

Upvotes: 0

Views: 214

Answers (2)

A. Walker
A. Walker

Reputation: 456

As explained by Honey's answer, TableViewCellIdentifiable doesn't provide enough type information for the compiler to work with. You could potentially adopt a different approach which changes the structure a bit (and is potentially overkill), but provides the functionality you're looking for:

internal protocol ValueAssociated { }

internal extension ValueAssociated {

    fileprivate var association: (label: String, value: Any?)? {
        get {
            let mirror = Mirror(reflecting: self)
            if let association = mirror.children.first, let label = association.label {
                return (label, association.value)
            }
            return nil
        }
    }
}

internal protocol CellIdentifiable {

    var rawValue: String { get }
}

internal enum CellType: Equatable, ValueAssociated {

    case portfolio(PortfolioIdentifier)
    case portfolioSelection(PortfolioSelectionIdentifier)
    case editPortfolio(EditPortfolioIdentifier)

    internal var identifier: String? {
        return (self.association?.value as? CellIdentifiable)?.rawValue
    }

    internal enum PortfolioIdentifier: String, Equatable, CellIdentifiable {

        case portfolioAsset = "portfolioAssetTableViewCell"
        case addAsset = "actionTableViewCell"
    }

    internal enum PortfolioSelectionIdentifier: String, Equatable, CellIdentifiable {

        case portfolio = "portfolioTableViewCell"
        case enterPortfolioDetails = "enterPortfolioDetailsTableViewCell"
        case addPortfolio = "actionTableViewCell"
    }

    internal enum EditPortfolioIdentifier: String, Equatable, CellIdentifiable {

        case editPortfolioName = "editPortfolioNameTableViewCell"
        case deletePortfolio = "deletePortfolioTableViewCell"
    }
}

This can be used as follows:

internal let cellType: CellType = .portfolio(.portfolioAsset)
print(cellType.identifier!) // Prints "portfolioAssetTableViewCell"

Hope this helps.

Upvotes: 1

mfaani
mfaani

Reputation: 36287

In your head, should the following code compile?

var x : Equatable

It shouldn't. Why?

Because if you had:

var x : Equatable 
var y : Equatable

Then the compiler can't ensure that x & y are of the same type. x can be "John", because "John"/Strings are Equatable...all while y can be 10, because 10/integers are equatable.

and the compiler would suspect that a few lines below you might want to do

if x == y { print ("equal" } 

which it can't process. So it just stops you from ever doing it in the beginning.


The following line of your code will trigger the same error because of the reason above.

internal let type: TableViewCellIdentifiable = TableViewCellType.EditPortfolio.editPortfolioName

Upvotes: 2

Related Questions