Introspecting Swift

3a0ae72b2f6bdc4476f1fcb63396e717?s=47 JP Simard
February 06, 2015

Introspecting Swift

Much has been said about all that was gained in The Great Swiftening of 2014, but let’s take a look at a great feature of Objective-C that was lost along the way: runtime introspection. In this talk, we’ll look at what we mean by runtime introspection, why it’s useful, why Swift’s support for it is limited and how we can build it into the language ourselves.

This presentation was given at dotSwift 2015 (http://dotswift.io).

3a0ae72b2f6bdc4476f1fcb63396e717?s=128

JP Simard

February 06, 2015
Tweet

Transcript

  1. !!Introspec+ng!Swi0!" dotSwi'(2015,(JP(Simard,(@simjp 1

  2. Why? 2

  3. Mantle @interface GHIssue : MTLModel <MTLJSONSerializing> @property GHUser *assignee; @property

    NSDate *updatedAt; @property NSString *title; @property NSString *body; @property NSDate *retrievedAt; @end 3
  4. FCModel(&(Realm @interface Employee : RLMObject @property NSString *name; @property NSDate

    *startDate; @property float salary; @property BOOL fullTime; @end 4
  5. How? 5

  6. 6

  7. The$six$ways$to$introspect*Swi- 1. S$ck'with'compile1$me'types'&'constraints 2. Apply'dynamic'cas$ng 3. Leverage'Swi?'s'MirrorType 4. Abuse'Objec$ve1C's'run$me 5.

    Use'private'func$ons 6. Resort'to'inspec$ng'memory'layout 7
  8. The$six$degrees$of$evil 1. S$ck'with'compile1$me'types'&'constraints'–'! 2. Apply'dynamic'cas$ng'–'" 3. Leverage'Swi@'s'MirrorType'–'# 4. Abuse'Objec$ve1C's'run$me'–'$ 5.

    Use'private'func$ons'–'% 6. Resort'to'inspec$ng'memory'layout'–'& 8
  9. Compile()me Types&&&Constraints 9

  10. QueryKit struct MyStruct { let intProp: Int struct Attributes {

    static let intProp = Attribute<Int>("intProp") } } // Usage let intProp = MyStruct.Attributes.intProp intProp > 0 // NSPredicate("intProp > 0"), typesafe 10
  11. Argo extension User: JSONDecodable { static func create(name: String)(email: String?)(role:

    Role) (friends: [User]) -> User { return User(name: name, email: email, role: role, friends: friends) } static func decode(j: JSONValue) -> User? { return User.create <^> j <| "name" <*> j <|? "email" // Use ? for parsing optional values <*> j <| "role" // Custom types conforming to JSONDecodable work <*> j <|| "friends" // parse arrays of objects } } 11
  12. Dynamic Cas$ng 12

  13. as? 13

  14. protocol XPCConvertible {} extension Int64: XPCConvertible {} extension String: XPCConvertible

    {} func toXPC(object: XPCConvertible) -> xpc_object_t? { switch(object) { case let object as Int64: return xpc_int64_create(object) case let object as String: return xpc_string_create(object) default: fatalError("Unsupported type for object: \(object)") return nil } } 14
  15. Official Swi$ Reflec%on 15

  16. /// The type returned by `reflect(x)`; supplies an API for

    runtime reflection on `x` protocol MirrorType { /// The instance being reflected var value: Any { get } /// Identical to `value.dynamicType` var valueType: Any.Type { get } /// A unique identifier for `value` if it is a class instance; `nil` otherwise. var objectIdentifier: ObjectIdentifier? { get } /// The count of `value`\ 's logical children var count: Int { get } subscript (i: Int) -> (String, MirrorType) { get } /// A string description of `value`. var summary: String { get } /// A rich representation of `value` for an IDE, or `nil` if none is supplied. var quickLookObject: QuickLookObject? { get } /// How `value` should be presented in an IDE. var disposition: MirrorDisposition { get } } 16
  17. struct MyStruct { let stringProp: String let intProp: Int }

    let reflection = reflect(MyStruct(stringProp: "a", intProp: 1)) for i in 0..<reflection.count { let propertyName = reflection[i].0 let value = reflection[i].1.value println("\(propertyName) = \(value)") if let value = value as? Int { println("int") } else if let value = value as? String { println("string") } } // stringProp = a // string // intProp = 1 // int 17
  18. Objec&ve(C Run$me 18

  19. import Foundation class objcSub: NSObject { let string: String? let

    int: Int? } var propCount: UInt32 = 0 let properties = clazz_copyPropertyList(objcSub.self, &propCount) for i in 0..<Int(propCount) { let prop = properties[i] String.fromCString(property_getName(prop))! // => "string" String.fromCString(property_getAttributes(prop))! // => "T@,N,R,Vstring" } 19
  20. Using Private Func%ons 20

  21. nm -a libswiftCore.dylib | grep "stdlib" > ... > __TFSs28_stdlib_getDemangledTypeNameU__FQ_SS

    > ... > _swift_stdlib_conformsToProtocol > _swift_stdlib_demangleName > _swift_stdlib_dynamicCastToExistential1 > _swift_stdlib_dynamicCastToExistential1Unconditional > _swift_stdlib_getTypeName > ... 21
  22. struct MyStruct { let stringProp: String let intProp: Int }

    let reflection = reflect(MyStruct(stringProp: "a", intProp: 1)) for i in 0..<reflection.count { let propertyName = reflection[i].0 let value = reflection[i].1.value println("\(propertyName) = \(value)") println(_stdlib_getDemangledTypeName(value)) } // stringProp = a // Swift.String // intProp = 1 // Swift.Int 22
  23. Inspec'ng Memory Layout 23

  24. 24

  25. 25

  26. struct _swift_data { unsigned long flags; const char *className; int

    fieldcount, flags2; const char *ivarNames; struct _swift_field **(*get_field_data)(); }; struct _swift_class { union { Class meta; unsigned long flags; }; Class supr; void *buckets, *vtable, *pdata; int f1, f2; // added for Beta5 int size, tos, mdsize, eight; struct _swift_data *swiftData; IMP dispatch[1]; }; 26
  27. class GenericClass<T> {} class SimpleClass: NSObject {} class ParentClass {

    let boolProp: Bool? // Optionals let intProp: Int // Without default value var floatProp = 0 as Float // With default value var doubleProp = 0.0 var stringProp = "" var simpleProp = SimpleClass() var genericProp = GenericClass<String>() } 27
  28. { "boolProp": "b", "intProp": "i", "floatProp": "f", "doubleProp": "d", "stringProp":

    "S", "simpleProp": "ModuleName.SimpleClass", "genericProp": "[Mangled GenericClass]" } 28
  29. Why? 29

  30. RealmSwi) class Employee: Object { dynamic var name = ""

    // you can specify defaults dynamic var startDate = NSDate() dynamic var salary = 0.0 dynamic var fullTime = true } class Company: Object { dynamic var name = "" dynamic var ceo: Employee? // optional let employees = List<Employee>() } 30
  31. The$six$ways$to$introspect*Swi- 1. S$ck'with'compile1$me'types'&'constraints'–'! 2. Apply'dynamic'cas$ng'–'" 3. Leverage'Swi@'s'MirrorType'–'# 4. Abuse'Objec$ve1C's'run$me'–'$ 5.

    Use'private'func$ons'–'% 6. Resort'to'inspec$ng'memory'layout'–'& 31
  32. Links&(1/2) • This&talk:&github.com/jpsim/talks • Mantle:&github.com/Mantle/Mantle • FCModel:&github.com/marcoarment/FCModel • Realm:&github.com/realm/realm9cocoa •

    QueryKit:&github.com/QueryKit/QueryKit • Argo:&github.com/thoughtbot/Argo 32
  33. Links&(2/2) • Dynamic)Cas,ng:)blog.segiddins.me • Swi1XPC:)github.com/jpsim/Swi5XPC • MirrorType)Docs:)swi5doc.org/protocol/MirrorType • Russ)Bishop)on)horrible)things:)russbishop.net •

    Injec,on)for)Xcode:)injec=onforxcode.com • Swi1IvarTypeDetector:)github.com/jpsim/Swi5IvarTypeDetector 33
  34. Thank&You! 34

  35. dotSwift().questions?.askThem! JP#Simard,#@simjp,#realm.io 35