Протокол Swift с associatedtype (неоднозначный для поиска типа)


Мне нужно создать универсальную функцию в протоколе с реализацией по умолчанию в расширении. It func должен работать с элементом как enum:RawRepresentable где RawValue == String всегда. Я пытался

protocol RequiresEnum: class {
    associatedtype SectionIdentifierEnum: RawRepresentable // how this add restriction to RawValue == String

    func test<T: SectionIdentifierEnum>(identifier: T) where T.RawValue == String
}

enum RequiresEnumDefault: String {
    case `default`
}

extension RequiresEnum where Self: UIViewController, SectionIdentifierEnum.RawValue == String {

    typealias SectionIdentifierEnum = RequiresEnumDefault

    func test<T: SectionIdentifierEnum>(identifier: T) where T.RawValue == String {
        print(T.rawValue)
    }

}

Но у меня есть ошибки

  • 'SectionIdentifierEnum' является неоднозначным для поиска типа в этом контексте
  • 'RawValue' не является членом типа 'T'

Любые решения

1 5

1 ответ:

В общем случае, когда речь идет о генериках в контексте протоколов, универсальный тип-заполнитель рассматривается как репрезентируемый associatedtype протокола. В вашем примере это будет SectionIdentifierEnum, который действует как заполнитель для ограниченного типа . SectionIdenfierEnum, однако, не является протоколом сам по себе, поэтому не может использовать его в качестве ограничения типа в универсальном методе. Вы можете, однако, использовать его в качестве самого типа в вашем методе test(...).


Swift 3.1

Сейчас, в настоящее время (Swift 3.1), вы не можете добавить сложные ограничения типа к associatedtype. Однако вы можете предоставить реализацию по умолчанию, доступную только для случая, когда where Self происходит от UIViewController и реализует протокол RequiresEnum, задав Тип SectionIdentifierEnum конкретному типу RequiresEnumDefault. Последний установит, что ассоциированный RawValue является String для этой реализации по умолчанию, поскольку RawValue конкретного типа RequiresEnumDefault является String.

Например:

// Swift 3.1
// ---------
// Types that implement this protocol mustn't necessarily use a
// `SectionIdentifierEnum` type where `SectionIdentifierEnum.RawValue` 
// is constrained to equal `String`. 
protocol RequiresEnum: class {
    associatedtype SectionIdentifierEnum: RawRepresentable

    func test(identifier: SectionIdentifierEnum)
}

enum RequiresEnumDefault: String {
    case `default`
}

// This extension, however, is only available for types that use
// `RequiresEnumDefault ` as the concrete type of `SectionIdentifierEnum`
// (in which case `SectionIdentifierEnum.RawValue` is `String`).
extension RequiresEnum where Self: UIViewController, SectionIdentifierEnum == RequiresEnumDefault {

    func test(identifier: SectionIdentifierEnum) {
        print(identifier.rawValue)
    }
}

// Example usage.
class MyViewController : UIViewController, RequiresEnum {
    typealias SectionIdentifierEnum = RequiresEnumDefault
    // ...
}

let foo = MyViewController()
foo.test(identifier: RequiresEnumDefault.default) 
  // prints "default" (using extension:s default implementation)

Выше, реализация по умолчанию test(...) доступен только тогда, когда SectionIdentifierEnum равен конкретному типу RequireEnumDefaultSelf происходит от UIViewController ...). Если вместо этого вы хотите, чтобы он был доступен только тогда, когда SectionIdentifierEnum является любым перечислением с типом String RawValue, вы изменяете ограничение типа расширений соответственно:

protocol RequiresEnum: class {
    associatedtype SectionIdentifierEnum: RawRepresentable

    func test(identifier: SectionIdentifierEnum)
}

enum RequiresEnumDefault: String {
    case `default`
}

extension RequiresEnum where Self: UIViewController, SectionIdentifierEnum.RawValue == String {

    func test(identifier: SectionIdentifierEnum) {
        print(identifier.rawValue)
    }
}


// Example usage.
enum EnumWithStringRawValue: String {
    case foo
}

class MyViewController : UIViewController, RequiresEnum {
    typealias SectionIdentifierEnum = EnumWithStringRawValue
    // ...
}

let foo = MyViewController()
foo.test(identifier: EnumWithStringRawValue.foo)
    // prints "foo" (using extension:s default implementation)

Как только Swift 4 будет выпущен, вы сможете добавить более сложные ограничения к associatedtype: s, согласно реализации предложения Swift evolution:

В этом случае вышеизложенное может быть изменено на:

// Swift 4
// -------
// Here, all types that implement this protocol must use a
// `SectionIdentifierEnum` type where `SectionIdentifierEnum.RawValue` 
// is equal to `String`. 
protocol RequiresEnum: class {
    associatedtype SectionIdentifierEnum: RawRepresentable 
        where SectionIdentifierEnum.RawValue == String

    func test(identifier: SectionIdentifierEnum)
}

enum RequiresEnumDefault: String {
    case `default`
}

// For the specific case where `SectionIdentifierEnum` equals
// `RequiresEnumDefault` (and where `Self` derives from `UIViewController`), 
// this default implementation is readily available.
extension RequiresEnum where Self: UIViewController, SectionIdentifierEnum == RequiresEnumDefault {

    func test(identifier: SectionIdentifierEnum) {
        print(identifier.rawValue)
    }

}

// Example usage.
class MyViewController : UIViewController, RequiresEnum {
    typealias SectionIdentifierEnum = RequiresEnumDefault
    // ...
}

let foo = MyViewController()
foo.test(identifier: RequiresEnumDefault.default) 
  // prints "default" (using extension:s default implementation)

Аналогично модифицируя ограничение на реализацию по умолчанию test(...) не только для случая, когда SectionIdentifierEnum равно RequiresEnumDefault (но и для любого перечисления: в этом случае мы знаем, что такое перечисление всегда будет иметь RawValue String, из-за ограничения на associatedtype в определении протокола).

protocol RequiresEnum: class {
    associatedtype SectionIdentifierEnum: RawRepresentable 
        where SectionIdentifierEnum.RawValue == String

    func test(identifier: SectionIdentifierEnum)
}

enum RequiresEnumDefault: String {
    case `default`
}

// From the constraint on the `RawValue` of `SectionIdentifierEnum`
// above, we know that `RawValue` equals `String`.
extension RequiresEnum where Self: UIViewController {

    func test(identifier: SectionIdentifierEnum) {
        print(identifier.rawValue)
    }
}

// Example usage.
enum EnumWithStringRawValue: String {
    case foo
}
class MyViewController : UIViewController, RequiresEnum {
    typealias SectionIdentifierEnum = EnumWithStringRawValue
    // ...
}

let foo = MyViewController()
foo.test(identifier: EnumWithStringRawValue.foo) 
  // prints "foo" (using extension:s default implementation)