Swift Type Metadata

Swift Type Metadata

try! Swift 2019
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

7a4968fbcd56e81f95a4f3c186141b52?s=128

Yuta Saito

March 22, 2019
Tweet

Transcript

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

  2. 2

  3. 3

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

  5. extension UITableView { func register<Cell>(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
  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
  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
  8. extension String { public init<Subject: CustomStringConvertible>(describing instance: Subject) { ...

    } public init<Subject>(describing instance: Subject) { ... } } let typeName = String(describing: Int.self) // "Int" 8
  9. extension Int.Type: CustomStringConvertible { // Cannot extend a metatype 'Int.Type'

    var description: String { return "Int" } } 9
  10. SwiftCore • Swift standard library • Fundamental types and interfaces

    SwiftRuntime • Swift runtime library • Dynamic behavior 10
  11. stdlib/public/core/Mirror.swift struct String { public init<Subject>(describing instance: Subject) { _print_unlocked(instance,

    &self) } } 11
  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<UInt8>, Int) 12
  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<UInt8>, Int) 12
  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<UInt8>, Int) 12
  15. 13

  16. 14

  17. struct StructMetadata { let kind: Int let typeDescriptor: UnsafePointer<StructTypeDescriptor> }

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

    struct StructTypeDescriptor { let flags: Int32 let parent: Int32 let name: RelativePointer<CChar> } 15 — docs/ABI/TypeMetadata.rst
  19. 16 — include/swift/Basic/RelativePointer.h

  20. func getTypeName<Subject>(of type: Subject.Type) -> String { let metadataPointer =

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

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

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

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

    unsafeBitCast( type, to: UnsafePointer<StructTypeMetadata>.self ) let namePointer: UnsafePointer<CChar> = metadataPointer.pointee .typeDescriptor.pointee .name.advancedPointer() return String(cString: namePointer) } 17
  25. let typeName = getTypeName(of: Int.self) // "Int" 18

  26. Use cases inside of Swift • Allocate instance • Value

    Witness Table • Dynamic method dispatch • VTable • Reflection • Mirror API 19
  27. ! 20

  28. Method swizzling 21

  29. Method swizzling class Animal { func bar() { print("bar") }

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

    func foo() { print("foo") } } struct ClassMetadata { ... // VTable var barRef: FunctionRef var fooRef: FunctionRef } 22
  31. let metadata = unsafeBitCast( Animal.self, to: UnsafeMutablePointer<ClassMetadata>.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
  32. let metadata = unsafeBitCast( Animal.self, to: UnsafeMutablePointer<ClassMetadata>.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
  33. let metadata = unsafeBitCast( Animal.self, to: UnsafeMutablePointer<ClassMetadata>.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
  34. let metadata = unsafeBitCast( Animal.self, to: UnsafeMutablePointer<ClassMetadata>.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
  35. let metadata = unsafeBitCast( Animal.self, to: UnsafeMutablePointer<ClassMetadata>.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
  36. Use cases • Zewo/Reflection • wickwirew/Runtime • alibaba/HandyJSON • kateinoigakukun/StubKit

    24
  37. alibaba/HandyJSON struct Item: HandyJSON { var name: String = ""

    var price: Double? var description: String? } if let item = Item.deserialize(from: jsonString) { // ... } 25
  38. Use cases • Zewo/Reflection • wickwirew/Runtime • alibaba/HandyJSON • kateinoigakukun/StubKit

    26
  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
  40. kateinoigakukun/StubKit 28

  41. kateinoigakukun/StubKit 29

  42. kateinoigakukun/StubKit func leafStub<T>(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
  43. kateinoigakukun/StubKit func enumStub<T>(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<T>(type: T.Type) -> Bool { let metadata = unsafeBitCast(type, to: UnsafePointer<EnumMetadata>.self).pointee return metadata.kind == 1 // kind value of enum is 1 } 31
  44. kateinoigakukun/StubKit func enumStub<T>(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<T>(type: T.Type) -> Bool { let metadata = unsafeBitCast(type, to: UnsafePointer<EnumMetadata>.self).pointee return metadata.kind == 1 // kind value of enum is 1 } 31
  45. kateinoigakukun/StubKit func enumStub<T>(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<T>(type: T.Type) -> Bool { let metadata = unsafeBitCast(type, to: UnsafePointer<EnumMetadata>.self).pointee return metadata.kind == 1 // kind value of enum is 1 } 31
  46. kateinoigakukun/StubKit func enumStub<T>(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<T>(type: T.Type) -> Bool { let metadata = unsafeBitCast(type, to: UnsafePointer<EnumMetadata>.self).pointee return metadata.kind == 1 // kind value of enum is 1 } 31
  47. Caution • ABI stability • Responsibility 32

  48. Summary • Swift uses metadata for dynamic behavior • We

    can use metadata in Swift • Let's write meta programming libraries! 33