Generally when covering generics in the context of protocols, the generic typeholder is seen as representable by an associatedtype
of the protocol. In your example this would be SectionIdentifierEnum
, which acts as a placeholder for a constrained type. SectionIdenfierEnum
is not, however, a protocol by itself, so cannot use it as a type constraint in a generic method. You can, however, use it as the type itself in your test(...)
method.
Swift 3.1
Now, currently (Swift 3.1), you can't add sophisticated type constrains to an associatedtype
. You could can, however, supply a default implementation available only for the case the where Self
derives from UIViewController
and implements the RequiresEnum
protocol by setting the SectionIdentifierEnum
type to the concrete RequiresEnumDefault
type. The latter will ascertain that the associated RawValue
is String
for this default implementation, as the RawValue
of the concrete RequiresEnumDefault
type is String
.
E.g.:
// 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)
Above, the default implementation of test(...)
is only available when SectionIdentifierEnum
equals the concrete type RequireEnumDefault
(and Self
derives from UIViewController
...). If instead you want it to only be available when SectionIdentifierEnum
is any enum with String
typed RawValue
, you modify the type constraint of the extensions accordingly:
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)
Once Swift 4 is released, you'll be able to add more sophisticated constraints to associatedtype
:s, as per the implementation of Swift evolution proposal:
In which case the above can be modified into:
// 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)
Likewise modifying the constraint on the default implementation of test(...)
not only to the case where SectionIdentifierEnum
equals RequiresEnumDefault
(but to any enum: in this case we know such enum will always have a RawValue
String
, due to the constraint on the associatedtype
in the protocol definition).
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)