$30 off During Our Annual Pro Sale. View Details »

Swift Type Metadata

Swift Type Metadata

try! Swift 2019

Video: https://www.youtube.com/watch?v=2fon4YFI9Ao
Script:
[en] http://bit.ly/2FAAZHO
[ja] http://bit.ly/2HIsGvR

Sample code:
GetTypeName.swift: http://bit.ly/2UVgQBB
Swizzle.swift: http://bit.ly/2UcIqwX

Yuta Saito

March 22, 2019
Tweet

More Decks by Yuta Saito

Other Decks in Programming

Transcript

  1. Swift Type Metadata
    @kateinoigakukun
    try! Swift 2019
    1

    View Slide

  2. 2

    View Slide

  3. 3

    View Slide

  4. let typeName = String(describing: Int.self)
    4

    View Slide

  5. extension UITableView {
    func register(nibWithCellClass: Cell.Type) where Cell: UITableViewCell {
    let typeName = String(describing: Cell.self)
    let nib = UINib(nibName: typeName, bundle: Bundle.main)
    register(nib, forCellReuseIdentifier: typeName)
    }
    }
    tableView.register(nibWithCellClass: TweetCell.self)
    5

    View Slide

  6. Agenda
    1. What is type metadata?
    2. Explore String(describing: Int.self)
    3. How to use metadata in Swift
    4. Use cases in OSS
    6

    View Slide

  7. What is type metadata?
    • Type information in Swift runtime
    • Used in Swift internal dynamic behavior
    • Metatype is pointer to metadata
    let metatype: Int.Type = Int.self
    7

    View Slide

  8. extension String {
    public init(describing instance: Subject) { ... }
    public init(describing instance: Subject) { ... }
    }
    let typeName = String(describing: Int.self) // "Int"
    8

    View Slide

  9. extension Int.Type: CustomStringConvertible { // Cannot extend a metatype 'Int.Type'
    var description: String {
    return "Int"
    }
    }
    9

    View Slide

  10. SwiftCore
    • Swift standard library
    • Fundamental types and interfaces
    SwiftRuntime
    • Swift runtime library
    • Dynamic behavior
    10

    View Slide

  11. stdlib/public/core/Mirror.swift
    struct String {
    public init(describing instance: Subject) {
    _print_unlocked(instance, &self)
    }
    }
    11

    View Slide

  12. stdlib/public/core/Misc.swift
    public func _typeName(_ type: Any.Type, qualified: Bool = true) -> String {
    let (stringPtr, count) = _getTypeName(type, qualified: qualified)
    return String._fromUTF8Repairing(
    UnsafeBufferPointer(start: stringPtr, count: count)).0
    }
    @_silgen_name("swift_getTypeName")
    public func _getTypeName(_ type: Any.Type, qualified: Bool) -> (UnsafePointer, Int)
    12

    View Slide

  13. stdlib/public/core/Misc.swift
    public func _typeName(_ type: Any.Type, qualified: Bool = true) -> String {
    let (stringPtr, count) = _getTypeName(type, qualified: qualified)
    return String._fromUTF8Repairing(
    UnsafeBufferPointer(start: stringPtr, count: count)).0
    }
    @_silgen_name("swift_getTypeName")
    public func _getTypeName(_ type: Any.Type, qualified: Bool) -> (UnsafePointer, Int)
    12

    View Slide

  14. stdlib/public/core/Misc.swift
    public func _typeName(_ type: Any.Type, qualified: Bool = true) -> String {
    let (stringPtr, count) = _getTypeName(type, qualified: qualified)
    return String._fromUTF8Repairing(
    UnsafeBufferPointer(start: stringPtr, count: count)).0
    }
    @_silgen_name("swift_getTypeName")
    public func _getTypeName(_ type: Any.Type, qualified: Bool) -> (UnsafePointer, Int)
    12

    View Slide

  15. 13

    View Slide

  16. 14

    View Slide

  17. struct StructMetadata {
    let kind: Int
    let typeDescriptor: UnsafePointer
    }
    struct StructTypeDescriptor {
    let flags: Int32
    let parent: Int32
    let name: RelativePointer
    }
    15 — docs/ABI/TypeMetadata.rst

    View Slide

  18. struct StructMetadata {
    let kind: Int
    let typeDescriptor: UnsafePointer
    }
    struct StructTypeDescriptor {
    let flags: Int32
    let parent: Int32
    let name: RelativePointer
    }
    15 — docs/ABI/TypeMetadata.rst

    View Slide

  19. 16 — include/swift/Basic/RelativePointer.h

    View Slide

  20. func getTypeName(of type: Subject.Type) -> String {
    let metadataPointer = unsafeBitCast(
    type, to: UnsafePointer.self
    )
    let namePointer: UnsafePointer = metadataPointer.pointee
    .typeDescriptor.pointee
    .name.advancedPointer()
    return String(cString: namePointer)
    }
    17

    View Slide

  21. func getTypeName(of type: Subject.Type) -> String {
    let metadataPointer = unsafeBitCast(
    type, to: UnsafePointer.self
    )
    let namePointer: UnsafePointer = metadataPointer.pointee
    .typeDescriptor.pointee
    .name.advancedPointer()
    return String(cString: namePointer)
    }
    17

    View Slide

  22. func getTypeName(of type: Subject.Type) -> String {
    let metadataPointer = unsafeBitCast(
    type, to: UnsafePointer.self
    )
    let namePointer: UnsafePointer = metadataPointer.pointee
    .typeDescriptor.pointee
    .name.advancedPointer()
    return String(cString: namePointer)
    }
    17

    View Slide

  23. func getTypeName(of type: Subject.Type) -> String {
    let metadataPointer = unsafeBitCast(
    type, to: UnsafePointer.self
    )
    let namePointer: UnsafePointer = metadataPointer.pointee
    .typeDescriptor.pointee
    .name.advancedPointer()
    return String(cString: namePointer)
    }
    17

    View Slide

  24. func getTypeName(of type: Subject.Type) -> String {
    let metadataPointer = unsafeBitCast(
    type, to: UnsafePointer.self
    )
    let namePointer: UnsafePointer = metadataPointer.pointee
    .typeDescriptor.pointee
    .name.advancedPointer()
    return String(cString: namePointer)
    }
    17

    View Slide

  25. let typeName = getTypeName(of: Int.self) // "Int"
    18

    View Slide

  26. Use cases inside of Swift
    • Allocate instance
    • Value Witness Table
    • Dynamic method dispatch
    • VTable
    • Reflection
    • Mirror API
    19

    View Slide

  27. !
    20

    View Slide

  28. Method swizzling
    21

    View Slide

  29. Method swizzling
    class Animal {
    func bar() { print("bar") }
    func foo() { print("foo") }
    }
    struct ClassMetadata {
    ...
    // VTable
    var barRef: FunctionRef
    var fooRef: FunctionRef
    }
    22

    View Slide

  30. Method swizzling
    class Animal {
    func bar() { print("bar") }
    func foo() { print("foo") }
    }
    struct ClassMetadata {
    ...
    // VTable
    var barRef: FunctionRef
    var fooRef: FunctionRef
    }
    22

    View Slide

  31. let metadata = unsafeBitCast(
    Animal.self, to: UnsafeMutablePointer.self
    )
    let bar = withUnsafeMutablePointer(to: &metadata.pointee.barRef) { $0 }
    let foo = withUnsafeMutablePointer(to: &metadata.pointee.fooRef) { $0 }
    bar.pointee = foo.pointee
    let animal = Animal()
    animal.bar() // foo
    23

    View Slide

  32. let metadata = unsafeBitCast(
    Animal.self, to: UnsafeMutablePointer.self
    )
    let bar = withUnsafeMutablePointer(to: &metadata.pointee.barRef) { $0 }
    let foo = withUnsafeMutablePointer(to: &metadata.pointee.fooRef) { $0 }
    bar.pointee = foo.pointee
    let animal = Animal()
    animal.bar() // foo
    23

    View Slide

  33. let metadata = unsafeBitCast(
    Animal.self, to: UnsafeMutablePointer.self
    )
    let bar = withUnsafeMutablePointer(to: &metadata.pointee.barRef) { $0 }
    let foo = withUnsafeMutablePointer(to: &metadata.pointee.fooRef) { $0 }
    bar.pointee = foo.pointee
    let animal = Animal()
    animal.bar() // foo
    23

    View Slide

  34. let metadata = unsafeBitCast(
    Animal.self, to: UnsafeMutablePointer.self
    )
    let bar = withUnsafeMutablePointer(to: &metadata.pointee.barRef) { $0 }
    let foo = withUnsafeMutablePointer(to: &metadata.pointee.fooRef) { $0 }
    bar.pointee = foo.pointee
    let animal = Animal()
    animal.bar() // foo
    23

    View Slide

  35. let metadata = unsafeBitCast(
    Animal.self, to: UnsafeMutablePointer.self
    )
    let bar = withUnsafeMutablePointer(to: &metadata.pointee.barRef) { $0 }
    let foo = withUnsafeMutablePointer(to: &metadata.pointee.fooRef) { $0 }
    bar.pointee = foo.pointee
    let animal = Animal()
    animal.bar() // foo
    23

    View Slide

  36. Use cases
    • Zewo/Reflection
    • wickwirew/Runtime
    • alibaba/HandyJSON
    • kateinoigakukun/StubKit
    24

    View Slide

  37. alibaba/HandyJSON
    struct Item: HandyJSON {
    var name: String = ""
    var price: Double?
    var description: String?
    }
    if let item = Item.deserialize(from: jsonString) {
    // ...
    }
    25

    View Slide

  38. Use cases
    • Zewo/Reflection
    • wickwirew/Runtime
    • alibaba/HandyJSON
    • kateinoigakukun/StubKit
    26

    View Slide

  39. kateinoigakukun/StubKit
    import StubKit
    struct User: Codable {
    let name: String
    let age: UInt
    }
    let user = try Stub.make(User.self)
    // User(name: "This is stub string", age: 12345)
    27

    View Slide

  40. kateinoigakukun/StubKit
    28

    View Slide

  41. kateinoigakukun/StubKit
    29

    View Slide

  42. kateinoigakukun/StubKit
    func leafStub(of type: T.Type) -> T {
    guard let stubbable = type as? Stubbable else { return nil }
    return type.stub
    }
    extension Int: Stubbable {
    var stub: Int { return 12345 }
    }
    extension enum: Stubbable { //
    !
    Can't extend
    var stub: Self {
    return enumStub()
    }
    }
    30

    View Slide

  43. kateinoigakukun/StubKit
    func enumStub(of type: T.Type) -> T? {
    if isEnum(type: type) {
    let rawValue = 0
    let rawPointer = withUnsafePointer(to: rawValue) { UnsafeRawPointer($0) }
    return rawPointer.assumingMemoryBound(to: T.self).pointee
    }
    return nil
    }
    func isEnum(type: T.Type) -> Bool {
    let metadata = unsafeBitCast(type, to: UnsafePointer.self).pointee
    return metadata.kind == 1 // kind value of enum is 1
    }
    31

    View Slide

  44. kateinoigakukun/StubKit
    func enumStub(of type: T.Type) -> T? {
    if isEnum(type: type) {
    let rawValue = 0
    let rawPointer = withUnsafePointer(to: rawValue) { UnsafeRawPointer($0) }
    return rawPointer.assumingMemoryBound(to: T.self).pointee
    }
    return nil
    }
    func isEnum(type: T.Type) -> Bool {
    let metadata = unsafeBitCast(type, to: UnsafePointer.self).pointee
    return metadata.kind == 1 // kind value of enum is 1
    }
    31

    View Slide

  45. kateinoigakukun/StubKit
    func enumStub(of type: T.Type) -> T? {
    if isEnum(type: type) {
    let rawValue = 0
    let rawPointer = withUnsafePointer(to: rawValue) { UnsafeRawPointer($0) }
    return rawPointer.assumingMemoryBound(to: T.self).pointee
    }
    return nil
    }
    func isEnum(type: T.Type) -> Bool {
    let metadata = unsafeBitCast(type, to: UnsafePointer.self).pointee
    return metadata.kind == 1 // kind value of enum is 1
    }
    31

    View Slide

  46. kateinoigakukun/StubKit
    func enumStub(of type: T.Type) -> T? {
    if isEnum(type: type) {
    let rawValue = 0
    let rawPointer = withUnsafePointer(to: rawValue) { UnsafeRawPointer($0) }
    return rawPointer.assumingMemoryBound(to: T.self).pointee
    }
    return nil
    }
    func isEnum(type: T.Type) -> Bool {
    let metadata = unsafeBitCast(type, to: UnsafePointer.self).pointee
    return metadata.kind == 1 // kind value of enum is 1
    }
    31

    View Slide

  47. Caution
    • ABI stability
    • Responsibility
    32

    View Slide

  48. Summary
    • Swift uses metadata for dynamic behavior
    • We can use metadata in Swift
    • Let's write meta programming libraries!
    33

    View Slide