Протокол 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 ответ:
В общем случае, когда речь идет о генериках в контексте протоколов, универсальный тип-заполнитель рассматривается как репрезентируемый
associatedtype
протокола. В вашем примере это будетSectionIdentifierEnum
, который действует как заполнитель для ограниченного типа .SectionIdenfierEnum
, однако, не является протоколом сам по себе, поэтому не может использовать его в качестве ограничения типа в универсальном методе. Вы можете, однако, использовать его в качестве самого типа в вашем методеtest(...)
.
Swift 3.1
Сейчас, в настоящее время (Swift 3.1), вы не можете добавить сложные ограничения типа к
associatedtype
. Однако вы можете предоставить реализацию по умолчанию, доступную только для случая, когда whereSelf
происходит от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
равен конкретному типуRequireEnumDefault
(иSelf
происходит от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)