Slide 1

Slide 1 text

@aligatr Slides & Code Snippets @aligatr tiny.cc/uikonf-codegen

Slide 2

Slide 2 text

Code Generation in Swift SwiftGen & Sourcery May 14-17
 2017 Olivier Halligon @aligatr 2 ✨

Slide 3

Slide 3 text

@aligatr UIFont(name: "Avenir-It ", size: 42) Avenir-Italics? Avenir-Oblique? AvenirItal? Avenir.ttc __HelvN_BI.ttf 3

Slide 4

Slide 4 text

@aligatr NSLocalizedString("home.gr ", comment: "") home.greeting? home.greetings? homeGreetings? 4

Slide 5

Slide 5 text

@aligatr let format = NSLocalizedString("home.greetings", comment: "") String(format: format, 1 , "UIKonf", "Berlin") "Berlin", 1, "UIKonf"? "UIKonf", "Berlin", 1? 1, "Berlin", "UIKonf"? 5

Slide 6

Slide 6 text

@aligatr let format = NSLocalizedString("home.greetings", comment: "") String(format: format, 1 , "UIKonf", "Berlin") Thread 1: EXC_BAD_ACCESS (code=1, address=0x1) "home.greetings" = "… %@ … %d … %@ …" 6

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

@aligatr Constants! label.text = L10n.homeGreetings("UIKonf", 1, "Berlin").string label.font = FontFamily.Avenir.blackOblique.font(size: 18) 8

Slide 9

Slide 9 text

@aligatr 9

Slide 10

Slide 10 text

@aligatr Code Generation! SwiftGen ✨ 10

Slide 11

Slide 11 text

@aligatr Code Generation swiftgen fonts "…/Fonts" … swiftgen strings "…/Localizable.strings" … swiftgen images "…/Assets.xcassets" … swiftgen storyboards "$PROJECT_DIR" … swiftgen colors "…/colors.xml" … … 11

Slide 12

Slide 12 text

@aligatr Constants for free! enum FontFamily { enum Avenir: String { case black = "Avenir-Black" case blackOblique = "Avenir-BlackOblique" case oblique = "Avenir-Oblique" case roman = "Avenir-Roman" func font(size: CGFloat) -> UIFont! { return Font(font: self.rawValue, size: size) } } } 12

Slide 13

Slide 13 text

@aligatr Constants for free! enum L10n { /// Welcome to %@, the #%d iOS conference in %@! case homeGreetings(String, Int, String) /// Dismiss changes? case editAlertTitle /// Changes have been made to the image « %@ » case editAlertMessage(String) /// Save case editAlertSave /// Dismiss case editAlertDismiss } 13

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

@aligatr enum Assets { static let apple = UIImage(named: "Apple") static let banana = UIImage(named: "Banana") static let orange = UIImage(named: "Orange") static let somePears = UIImage(named: "Some-Pears") } 15 enum Assets: String { case apple = "Apple" case banana = "Banana" case orange = "Orange" case somePears = "Some-Pears" var image: UIImage { return UIImage(named: self.rawValue) } }

Slide 16

Slide 16 text

@aligatr Templates! 16

Slide 17

Slide 17 text

@aligatr A Stencil Template enum Assets { {% for image in images %} static let {{image|swiftIdentifier}}
 = UIImage(named: "{{image}}") {% endfor %} } 17

Slide 18

Slide 18 text

@aligatr A Stencil Template enum Assets: String { {% for image in images %} case {{image|swiftIdentifier|lowerFirstWord}} = "{{image}}" {% endfor %} var image: UIImage { return UIImage(asset: self) } } extension UIImage { convenience init!(asset: Assets) { self.init(named: asset.rawValue) } } 18

Slide 19

Slide 19 text

@aligatr Profit! ✓ Free auto-completion + discovery (font names, …) ✓ Type-safety (typos, types in format strings, …) ✓ Customizable ✓ Generate anything (documentation, even ObjC ) ✓ No more maintenance 19

Slide 20

Slide 20 text

@aligatr

Slide 21

Slide 21 text

@aligatr What about boilerplate code? struct ImageInfo: Equatable { let title: String let author: String let date: Date } func == (lhs: ImageInfo, rhs: ImageInfo) -> Bool { guard lhs.title == rhs.title else { return false } guard lhs.author == rhs.author else { return false } guard lhs.date == rhs.date else { return false } return true } 21

Slide 22

Slide 22 text

@aligatr What about boilerplate code? struct ImageInfo: Equatable { let title: String let author: String let date: Date let cameraModel: String let kind: ImageKind } func == (lhs: ImageInfo, rhs: ImageInfo) -> Bool { guard lhs.title == rhs.title else { return false } guard lhs.author == rhs.author else { return false } guard lhs.date == rhs.date else { return false } return true } 22

Slide 23

Slide 23 text

@aligatr What about boilerplate code? 23

Slide 24

Slide 24 text

@aligatr Sourcery! 24

Slide 25

Slide 25 text

@aligatr MetaProgramming! Parser Internal Representation Stencil Template Generated Code Template Engine Your code (SourceKitten) 25

Slide 26

Slide 26 text

@aligatr There are {{types.all.count}} types in this code, including: - {{types.enums.count}} enums - {{types.structs.count}} structs - {{types.classes.count}} classes There are 22 types in this code, including: - 11 enums - 1 structs - 6 classes Introspect your code Template Generated Code 26

Slide 27

Slide 27 text

@aligatr What do we want? Equatable! struct ImageInfo { let title: String let author: String let date: Date } Your code 27

Slide 28

Slide 28 text

@aligatr What do we want? Equatable! extension ImageInfo: Equatable {} func == (lhs: ImageInfo, rhs: ImageInfo) -> Bool { guard lhs.title == rhs.title
 else { return false } guard lhs.author == rhs.author
 else { return false } guard lhs.date == rhs.date
 else { return false } return true } Generated Code we want 28

Slide 29

Slide 29 text

@aligatr How do we want it? Automatically! {% for type in types.implementing.AutoEquatable %} extension {{ type.name }}: Equatable {} func == (lhs: {{ type.name }}, rhs: {{ type.name }}) -> Bool { {% for variable in type.storedVariables %}
 guard lhs.{{ variable.name }} == rhs.{{ variable.name }}
 else { return false } {% endfor %} return true } {% endfor %} Template 29

Slide 30

Slide 30 text

@aligatr How do we want it? Automatically! struct ImageInfo : AutoEquatable { let title: String let author: String let date: Date } Your code $ sourcery/bin/sourcery + = 30

Slide 31

Slide 31 text

@aligatr JSON Parsing struct Contact { let id: String let firstName: String let lastName: String } 31

Slide 32

Slide 32 text

@aligatr JSON Parsing: Boooriiing! extension Contact: JSONDeserializable { init?(json: [String: Any]) { self.id = json["id"] as? String self.firstName = json["first_name"] as? String self.lastName = json["last_name"] as? String self.dateOfBirth = (json["dob"] as? String)
 .flatMap(JSONDateFormatter.date(from:)) self.avatar = (json["avatar"] as? [String: Any])
 .flatMap(Avatar.init(json:)) } } 32

Slide 33

Slide 33 text

@aligatr Code Gen to the rescue! {% for type in types.implementing.AutoJSONDeserializable %} extension {{ type.name }}: JSONDeserializable { init?(json: [String: Any]) { {% for prop in type.storedVariables %} // ? {% endfor %} } } {% endfor %} 33

Slide 34

Slide 34 text

@aligatr Live Templating daemon mode to write your templates live: $ sourcery/bin/sourcery --watch 34

Slide 35

Slide 35 text

@aligatr First attempt {% for prop in type.storedVariables %} self.{{prop.name}} = json["{{prop.name}}"] as? {{prop.typeName}} {% endfor %} self.id = json["id"] as? String self.firstName = json["firstName"] as? String self.lastName = json["lastName"] as? String 35

Slide 36

Slide 36 text

@aligatr Sourcery Annotations! struct Contact { let id: String // sourcery: JSONKey = "first_name" let firstName: String // sourcery: JSONKey = "last_name" let lastName: String } {% for prop in type.storedVariables %} json["{{ prop.annotations.JSONKey |default:prop.name }}"] {% endfor %} 36

Slide 37

Slide 37 text

@aligatr Sourcery Annotations! {% for prop in type.storedVariables %} self.{{prop.name}} = json["{{ prop.annotations.JSONKey |default:prop.name }}"] as? {{prop.typeName}} {% endfor %} self.id = json["id"] as? String self.firstName = json["first_name"] as? String self.lastName = json["last_name"] as? String 37

Slide 38

Slide 38 text

@aligatr 38

Slide 39

Slide 39 text

@aligatr Save tons of code already! struct Customer: AutoEquatable, AutoJSONDeserializable {
 … // 15 properties
 } struct Product: AutoEquatable, AutoJSONDeserializable { … // 28 properties } struct Cart: AutoEquatable, AutoJSONDeserializable { … // 12 properties } struct Order: AutoEquatable, AutoJSONDeserializable { … // 17 properties } enum ShippingOption: AutoCases { … // 9 cases } 39

Slide 40

Slide 40 text

@aligatr Type Erasure let list: [AnyPokemon] = … // sourcery: TypeErase = PokemonType protocol Pokemon { associatedtype PokemonType func attack(move: PokemonType) } 40 github.com/AliSoftware/SourceryTemplates + =

Slide 41

Slide 41 text

@aligatr Why stop there? • Lenses • Decorators • Property-Based Testing (SwiftCheck) • … 41

Slide 42

Slide 42 text

@aligatr Going Further • Building an API Client using Sourcery Annotations • littlebitesofcocoa.com/295-building-an-api-client-with-sourcery- key-value-annotations • Generate JSON Parsing code • github.com/Liquidsoul/Sourcery-AutoJSONSerializable • Automatic Type Erasure • github.com/AliSoftware/SourceryTemplates 42

Slide 43

Slide 43 text

@aligatr Going Further • Krzysztof’s talk at CraftConf about Sourcery • https://www.ustream.tv/recorded/102903026 • My Live Demo of SwiftGen at NSBudapest • Slides: speakerdeck.com/alisoftware • Video: http://www.ustream.tv/recorded/103135632 • Improve SwiftGen • We love contributors! ❤ (Free push access) 43

Slide 44

Slide 44 text

@aligatr Hello, I’m Olivier github.com/SwiftGen/SwiftGen github.com/krzysztofzablocki/Sourcery github.com/AliSoftware (OHHTTPStubs, Reusable, …) alisoftware.github.io @aligatr ✨ 44 speakerdeck.com/alisoftware