Slide 1

Slide 1 text

Swift Type Metadata @kateinoigakukun try! Swift 2019 1

Slide 2

Slide 2 text

2

Slide 3

Slide 3 text

3

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

13

Slide 16

Slide 16 text

14

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

16 — include/swift/Basic/RelativePointer.h

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

! 20

Slide 28

Slide 28 text

Method swizzling 21

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

kateinoigakukun/StubKit 28

Slide 41

Slide 41 text

kateinoigakukun/StubKit 29

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

Caution • ABI stability • Responsibility 32

Slide 48

Slide 48 text

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