For every type T in Swift, there is an associated metatype T.Type.
Let's try to write a generic function like staticSizeof. We will only consider its declaration; implementation is trivial and unimportant here.
Out first try would be:
func staticSizeof<T>() -> Int
staticSizeof<Float>() // error :(Unfortunately, it's an error. We can't explicitly specialize generic functions in Swift. Second try: we pass a parameter to our function and get generic type parameter from it:
func staticSizeof<T>(_: T) -> Int
staticSizeof(1) //=> should be 8But what if our type T was a bit more complex and hard to obtain? For example, think of struct Properties that loads a file on initialization:
let complexValue = Properties("the_file.txt") // we have to load a file
staticSizeof(complexValue) // just to specialize a functionIsn't that weird? But we can work around that limitation by passing instance of a dummy generic type:
struct Dummy<T> { }
func staticSizeof<T>(_: Dummy<T>) -> Int
staticSizeof(Dummy<Float>())This is the main detail to understand: we can explicitly specialize generic types, and we can infer generic type parameter of function from generic type parameter of passed instance. Now, surprise! We've already got Dummy<T> in the language: it's called T.Type and created using T.self:
func staticSizeof<T>(_: T.Type) -> Int
staticSizeof(Float.self)But there's a lot more to T.Type. Sit tight.
Internally, T.Type stores identifier of a type, which is an Int value. Specifically, T.Type can refer to any subtype of T. With enough luck, we can also cast instances of metatypes to other metatypes. For example, Int: CustomStringConvertible, so we can do this:
let subtype = Int.self
metaInt //=> Int
let supertype = subtype as CustomStringConvertible.Type
supertype //=> IntHere, supertype: CustomStringConvertible.Type can refer to Int, to String or to any other T: CustomStringConvertible.
We can also use as?, as! and is to check subtyping relationships. I'll only show examples with is:
Int.self is CustomStringConvertible.Type //=> trueprotocol Base { }
protocol Derived: Base { }
Derived.self is Base.Type //=> trueprotocol Base { }
struct Derived: Base { }
let someBase = Derived.self as Base.Type
// ...
someBase is Derived.Type //=> trueA common practise is to store metatypes as Any.Type. When needed, we can check all required conformances.
If we have an instance of T.Type, we can call static methods of T on it:
struct MyStruct {
static func staticMethod() -> String { return "Hello metatypes!" }
}
let meta = MyStruct.self
meta.staticMethod() //=> Hello metatypes!What is especially useful, if our T.self actually stores some U:T, then static method of U will be called:
protocol HasStatic { static func staticMethod() -> String }
struct A: HasStatic { static func staticMethod() -> String { return "A" } }
struct B: HasStatic { static func staticMethod() -> String { return "B" } }
var meta: HasStatic.Type
meta = A.self
meta.staticMethod() //=> A
meta = B.self
meta.staticMethod() //=> BSumming that up, metatypes have far deeper semantics than a tool for specialization of generic functions. They combine dynamic information about a type with static information "contained type is a subtype of this". They can also dynamically dispatch static methods the same way as normal methods are dynamically dispatched.
For protocols P, besides normal P.Type, there is also a "restricting metatype" P.Protocol that is the same as P.Type, except that it can only reflect P itself and not any of its subtypes:
Int.self is CustomStringConvertible.Type //=> true
Int.self is CustomStringConvertible.Protocol //=> falseI personally don't understand, why P.Protocol is needed. Even without P.Protocol, we can test for equality:
Int.self is CustomStringConvertible.Type //=> true
Int.self == CustomStringConvertible.self //=> falseFor protocols P, P.self returns a P.Protocol, not P.Type:
let meta = CustomStringConvertible.self
print(dynamicType(meta)) //=> CustomStringConvertible.ProtocolIn practise, the existence of P.Protocol creates problems. If T is a generic parameter, then T.Type turns into P.Protocol if a protocol P is passed:
func printMetatype<T>(_ meta: T.Type) {
print(dynamicType(meta))
let copy = T.self
print(dynamicType(copy))
}
printMetatype(CustomStringConvertible.self) //=> CustomStringConvertible.Protocol x2Finally, we can understand the confusing situation!
func doesIntConformTo<T>(_: T.Type) -> Bool {
return Int.self is T.Type
}
doesIntConformTo(CustomStringConvertible.self) //=> false (?!)Now we understand that because T is a protocol, T.Type turns into a .Protocol, and we get the confusing behaviour.
The suggestion is to remove P.Protocol from the language.