I have a protocol :
public protocol PropertyIdentifierType
{
associatedtype PropertyIdentifier : Hashable & RawRepresentable where PropertyIdentifier.RawValue == String
}
This is so I can "destringify" references to the properties of a type. e.g.
public class Model : PropertyIdentifierType
{
public enum PropertyIdentifier : String
{
case name
}
public var name: String
}
Now I want to be able to convert a string property name (from an object in the Xcode designer) into the appropriate identifier :
func identifierFromName<identifierTypeT : PropertyIdentifierType>(name: String) -> identifierTypeT.PropertyIdentifier?
{
return identifierTypeT.PropertyIdentifier(rawValue: propertyName)
}
But I am getting the error :
Generic parameter 'identifierTypeT' is not used in function signature
Now, I've only been using generics for around 15 years (in C# before Swift) but, as far as I can see, it is used as part of the return type of the function.
Can anyone kindly tell me what I am missing?
identifierTypeT
is not used directly in the function signature; you only used one of its associated types, identifierTypeT.PropertyIdentifier
.
That is not enough for the compiler to know which specialization to use, since the same associated type may be shared across many conformers:
struct A : PropertyIdentifierType {
typealias PropertyIdentifier = X
}
struct B : PropertyIdentifierType {
typealias PropertyIdentifier = X
}
// Should the following use A or B...?
let identifier: X? = identifierFromName(name: "...")
Here is one possible solution:
func identifier<T : PropertyIdentifierType>(
from name: String,
for type: T.Type) -> T.PropertyIdentifier? {
// ...
}
let resolved = identifier(from: "...", for: SomeConformingType.self)
I think the reason the compiler raises this issue is because right now we don't allow to resolve the generic type parameters on functions directly from the function call side such as identifierFromName<MyType>(name: ...)
.
Therefore the compiler asks for a way to obtain that type information, and the one of two ways is through function parameters.
OK. In the end, I took the associatedtype out of the protocol, used a type eraser, and did the following (the names are different but the principle is the same)
public protocol AnyStringEnumType { } // type eraser
public protocol StringEnumType : AnyStringEnumType, Hashable, RawRepresentable where Self.RawValue == String { }
public protocol CommandIdentifierDeclaration
{
func identifier(for name: String) -> AnyStringEnumType?
}
In a class implementing CommandIdentifierDeclaration :
public enum ValueModelCommandIdentifier : String, StringEnumType
{
case setValue
case saveModelSelection
}
public class ValueModelCommandSet : CommandIdentifierDeclaration
{
public func identifier(for name: String) -> AnyStringEnumType?
{
return ValueModelCommandIdentifier(rawValue: name)
}
…
}
Finally, in the calling code :
guard let commandSet = model.commandSet as? CommandIdentifierDeclaration,
let identifier = commandSet.identifier(for: commandName) else
{
fatalError("Invalid command identifier")
}
In the end, Swift's version of generics and the dreaded PAT problems with protocols took me a couple of days to solve
Of course, if Xcode allowed @IBInspectable on enum types, with a nice drop-down chooser, none of this would have been necessary