SwiftのMetatypeとMetadata

(2020-06-25)

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)

参考

Swift Type metadata - Qiita

Swift Type Metadata (ja) - kateinoigakukunのブログ