Home > OS >  In Swift, repeated "Type '' does not conform to protocol ''" after add
In Swift, repeated "Type '' does not conform to protocol ''" after add

Time:07-17

Bizarre, repeated error. In Swift, repeated "Type '' does not conform to protocol ''" after clicking to add stubs. If I click to add the stubs, they appear, but the error doesn't go away; clicking again creates duplicates that say that they're redefinitions. I'm guessing the issues is with the generics, but I'm not sure how.

Yes, I've tried restarting Xcode.

Here's a photo of the error and then the code itself.

Swift error

Code in a Swift Playground:

// MARK - Locale
protocol LocaleStrategy: Codable {
    var locale: String? { get set }
}

struct LocaleStrategyNone: LocaleStrategy {
    var locale: String? = nil
}

struct LocaleStrategyCountry: LocaleStrategy {
    var locale: String?
}

// MARK - TimeWindow
protocol TimeWindowStrategy: Codable {
    var timeWindowInSeconds: Int? { get set }
}

struct TimeWindowStrategyNone: TimeWindowStrategy {
    var timeWindowInSeconds: Int? = nil
}

struct TimeWindowStrategyInSeconds: TimeWindowStrategy {
    var timeWindowInSeconds: Int?
}

// MARK - Project
protocol ProjectProtocol: Codable {
    var localeStrategy: LocaleStrategy { get set }
    var timeWindowStrategy: TimeWindowStrategy { get set }
}

struct Project<LocaleStrategy: Codable, TimeWindowStrategy: Codable>: ProjectProtocol { // <-- ERROR: Type 'Project<LocaleStrategy, TimeWindowStrategy>' does not conform to protocol 'ProjectProtocol'
    var localeStrategy: LocaleStrategy
    var timeWindowStrategy: TimeWindowStrategy
}

let localeStrategyCountry = LocaleStrategyCountry(locale: "USA")
let timeStrategyNone = TimeWindowStrategyNone()
let project = Project(localeStrategy: localeStrategyCountry, timeWindowStrategy: timeStrategyNone)

let localeStrategyNone = LocaleStrategyNone()
let timeWindowStrategyInSeconds = TimeWindowStrategyInSeconds(timeWindowInSeconds: 3)
let project2 = Project(localeStrategy: localeStrategyNone, timeWindowStrategy: timeWindowStrategyInSeconds)

let projects: [ProjectProtocol] = [project, project2]

CodePudding user response:

The way you're doing this is difficult in Swift 5.6, and often leads people to reach for type erasers, but if your code is anything like your example, there's a much better design. Rather than a protocol, just use a struct directly:

struct LocaleStrategy: Codable {
    var locale: String?

    static var none = Self(locale: nil)
    static func country(locale: String) -> Self { Self(locale: locale) }
}

// MARK - TimeWindow
struct TimeWindowStrategy: Codable {
    var timeWindowInSeconds: Int?

    static var none = Self(timeWindowInSeconds: nil)
    static func inSeconds(_ timeWindowInSeconds: Int) -> Self { Self(timeWindowInSeconds: timeWindowInSeconds)}
}

Then Project doesn't need to be generic, and the rest (including the question of Codable) is much more straightforward with less code:

struct Project: ProjectProtocol {
    var localeStrategy: LocaleStrategy
    var timeWindowStrategy: TimeWindowStrategy
}

I realize that your full project may not be as simple as this, but if it can be thought of as different ways to initialize a single type rather than different types, then this all gets much simpler.

In Swift 5.7, automatic type erasers via any are easier to create (see Joakim Danielson's answer), but when possible you should still avoid them. Simple structs are still much simpler to work with and faster.

CodePudding user response:

You did not use the right protocols. You need to use them instead of Codable.

Replace Scratchpad with the name of your module:

protocol ProjectProtocol: Codable {
  associatedtype LocaleStrategy: Scratchpad.LocaleStrategy
  associatedtype TimeWindowStrategy: Scratchpad.TimeWindowStrategy

  var localeStrategy: LocaleStrategy { get set }
  var timeWindowStrategy: TimeWindowStrategy { get set }
}

struct Project<
  LocaleStrategy: Scratchpad.LocaleStrategy,
  TimeWindowStrategy: Scratchpad.TimeWindowStrategy
>: ProjectProtocol {
  var localeStrategy: LocaleStrategy
  var timeWindowStrategy: TimeWindowStrategy
}

This is the way to organize code for "Static Member Lookup in Generic Contexts":

public extension TimeWindowStrategy where Self == TimeWindowStrategyNone {
  static var none: Self { .init() }
}

public extension TimeWindowStrategy where Self == TimeWindowStrategyInSeconds {
  static func inSeconds(_ timeWindowInSeconds: Int) -> Self {
    .init(timeWindowInSeconds: timeWindowInSeconds)
  }
}

public extension LocaleStrategy where Self == LocaleStrategyNone {
  static var none: Self { .init() }
}

public extension LocaleStrategy where Self == LocaleStrategyCountry {
  static func country(locale: String) -> Self {
    .init(locale: locale)
  }
}
let project = Project(
  localeStrategy: .country(locale: "USA"),
  timeWindowStrategy: .none
)

let project2 = Project(
  localeStrategy: .none,
  timeWindowStrategy: .inSeconds(3)
)

The array requires Swift >= 5.7, but is not part of this question.

let projects: [any ProjectProtocol] = [project, project2]

CodePudding user response:

This is a solution for Swift 5.7 but not for earlier versions of Swift.

You can use associatedtype with your protocol

protocol ProjectProtocol: Codable {
    associatedtype LocStrategy: LocaleStrategy
    associatedtype TWStrategy: TimeWindowStrategy
    var localeStrategy: LocStrategy { get set }
    var timeWindowStrategy: TWStrategy { get set }
}

And then use them in your struct

struct Project<L: LocaleStrategy, T: TimeWindowStrategy>: ProjectProtocol {
    var localeStrategy: L
    var timeWindowStrategy: T
}

This requires the array declaration to change

let projects: [any ProjectProtocol] = [project, project2]
  • Related