ObjcのClass型のように
インスタンスではなくクラスそのものを取りたい場合、SwiftではFoo.Type
で表せるMetatypeを用いる。
値はクラスからはFoo.self
で、インスタンスからはtype(of: Foo())
で得られる。
初期化の際はサブクラスにも存在することが保証されるrequired initializerを呼ぶか、継承できないfinal classである必要がある。
Swiftのdesignated/convenience/required/default initializerと継承 - sambaiz-net
class Foo {
required init() {}
func aaa() -> String {
return "foo.aaa"
}
}
class Bar: Foo {
override func aaa() -> String {
return "bar.aaa"
}
}
class Hoge {
func aaa() -> String {
return "hoge.aaa"
}
}
func initFooAndCallFunc(type: Any.Type) -> String {
guard let fooType = type as? Foo.Type else {
return "this is not Foo"
}
return fooType.init().aaa()
}
print(initFooAndCallFunc(type: Foo.self)) // foo.aaa
print(initFooAndCallFunc(type: Bar.self)) // bar.aaa
print(initFooAndCallFunc(type: Hoge.self)) // this is not Foo
print(initFooAndCallFunc(type: type(of: Foo()))) // foo.aaa
あまり意識することはないが、Metatypeは実行時に用いられる型情報であるType Metadataへのポインタになっている。クラスやenumといった種類ごとに異なる構造のMetadataがあって、例えばClass Metadataにはスーパークラスのポインタやインスタンスサイズなどが含まれている。
仕様通りの順番でstructを定義し、unsafebitcast()でキャストすると値が確認できる。
struct ClassMetadata {
let isaPointer: Int
let superPointer: Int
let objcRuntimeReserved1: Int
let objcRuntimeReserved2: Int
let rodataPointer: Int
let classFlags: Int32
let instanceAddressPoint: Int32
let instanceSize: Int32
}
let metadataPointer = unsafeBitCast(Bar.self, to: UnsafePointer<ClassMetadata>.self)
let metadata = metadataPointer.pointee
print(metadata.instanceSize) // 16
// = class_getInstanceSize(Bar.self)