Slide 1

Slide 1 text

Understanding Codable iOSCon London 22 March 2018 Ian Partridge @alfa

Slide 2

Slide 2 text

Ian Partridge • Kitura team leader • Background in runtime systems • Java Virtual Machines • Runtime performance analysis • Full-stack debugging • www.twitter.com/alfa • www.github.com/ianpartridge

Slide 3

Slide 3 text

Codable

Slide 4

Slide 4 text

Serialization for Swift - Design Goals • Tightly integrated with Swift’s type system • Bridge the divide between strongly typed language and weakly typed data formats • Comprehensive • Equal support for structs, classes and enums • Follow “Kay’s maxim” • “Simple things should be simple, complex things should be possible”

Slide 5

Slide 5 text

struct Profile { var name: String var photo: Data var dateOfBirth: Date } let profile = Profile(name: “Ian”, photo: image, dateOfBirth: Date(“28-11-1980”))

Slide 6

Slide 6 text

struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } let profile = Profile(name: “Ian”, photo: image, dateOfBirth: Date(“28-11-1980”))

Slide 7

Slide 7 text

struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } let profile = Profile(name: “Ian”, photo: image, dateOfBirth: Date(“28-11-1980”)) let encoder = JSONEncoder() let json: Data = try! encoder.encode(profile) let decoder = JSONDecoder() let person: Profile = try! decoder.decode(Profile.self, from: json)

Slide 8

Slide 8 text

/// A type that can convert itself into and out of an external representation. public typealias Codable = Decodable & Encodable /// A type that can encode itself to an external representation. public protocol Encodable { /// Encodes this value into the given encoder. public func encode(to encoder: Encoder) throws } /// A type that can decode itself from an external representation. public protocol Decodable { /// Creates a new instance by decoding from the given decoder. public init(from decoder: Decoder) throws }

Slide 9

Slide 9 text

Automatic Codable Conformances • Standard library • Array, Dictionary, Set • Int, UInt, Int32…, Float, Double, Bool, String • Optional • CoreGraphics • CGPoint, CGRect, CGSize, CGVector • Foundation • Data, TimeZone, Measurement, UUID, URL, URLComponents, NSRange, Locale, IndexSet, IndexPath, AffineTransform, Decimal, Date, DateComponents, DateInterval, CharacterSet, Calendar

Slide 10

Slide 10 text

Encoders and Decoders • JSONEncoder • JSONDecoder • PropertyListEncoder • PropertyListDecoder

Slide 11

Slide 11 text

Coding Keys • A private nested enum inside each conforming type • One case for every stored property • Synthesised by the compiler automatically whenever possible • Create your own if you want to override the automatic behaviour • Renaming properties to/from the encoded version • Ignoring certain properties during encoding

Slide 12

Slide 12 text

/// A type that can be used as a key for encoding and decoding. public protocol CodingKey { /// The string to use in a named collection /// (e.g. a string-keyed dictionary). var stringValue: String { get } init?(stringValue: String) /// The value to use in an integer-indexed collection /// (e.g. an int-keyed dictionary). var intValue: Int? { get } init?(intValue: Int) }

Slide 13

Slide 13 text

extension Profile: Encodable { private enum CodingKeys: String, CodingKey { case name = "name"
 case photo = "photo" case dateOfBirth = "dateOfBirth" } }

Slide 14

Slide 14 text

extension Profile: Encodable { func encode(to encoder: Encoder) throws { } }

Slide 15

Slide 15 text

/// A type that can encode values into a native format for external representation. public protocol Encoder { /// Returns an encoding container appropriate for holding /// multiple values keyed by the given key type. func container(keyedBy: Key.Type) -> KeyedEncodingContainer /// Returns an encoding container appropriate for holding /// multiple unkeyed values. func unkeyedContainer() -> UnkeyedEncodingContainer /// Returns an encoding container appropriate for holding /// a single primitive value. func singleValueContainer() -> SingleValueEncodingContainer }

Slide 16

Slide 16 text

extension Profile: Encodable { func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self)
 try container.encode(self.name, forKey: .name) try container.encode(self.photo, forKey: .photo) try container.encode(self.dateOfBirth, forKey: .dateOfBirth) } } extension Profile: Decodable { init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self)
 self.name = try values.decode(String.self, forKey: .name) self.photo = try values.decode(Data.self, forKey: .photo) self.dateOfBirth = try values.decode(Date.self, forKey: .dateOfBirth) } }

Slide 17

Slide 17 text

Web APIs

Slide 18

Slide 18 text

SWAPI The Star Wars API www.swapi.co

Slide 19

Slide 19 text

$ http -b https://swapi.co/api/ { "films": "https://swapi.co/api/films/", "people": "https://swapi.co/api/people/", "planets": "https://swapi.co/api/planets/", "species": "https://swapi.co/api/species/", "starships": "https://swapi.co/api/starships/", "vehicles": "https://swapi.co/api/vehicles/" }

Slide 20

Slide 20 text

import Foundation struct API: Decodable { let films: URL let people: URL let planets: URL let species: URL let starships: URL let vehicles: URL }

Slide 21

Slide 21 text

import Foundation import PlaygroundSupport let session = URLSession(configuration: .default) let apiURL = URL(string: "https://swapi.co/api/")! let request = URLRequest(url: apiURL) let task = session.dataTask(with: request) { data, response, error in guard error == nil, let data = data else { return } let decoder = JSONDecoder() do { let api = try decoder.decode(API.self, from: data) print(api) } catch let error { print(error) } PlaygroundPage.current.finishExecution() } task.resume() PlaygroundPage.current.needsIndefiniteExecution = true

Slide 22

Slide 22 text

API(films: https://swapi.co/api/films/, people: https://swapi.co/api/people/, planets: https://swapi.co/api/planets/, species: https://swapi.co/api/species/, starships: https://swapi.co/api/starships/, vehicles: https://swapi.co/api/vehicles/)

Slide 23

Slide 23 text

$ http -b https://swapi.co/api/films/ { "count": 7, "next": null, "previous": null, "results": [ { "characters": [ "https://swapi.co/api/people/1/", "https://swapi.co/api/people/2/", "https://swapi.co/api/people/3/", "https://swapi.co/api/people/4/", "https://swapi.co/api/people/5/", "https://swapi.co/api/people/6/", "https://swapi.co/api/people/7/", "https://swapi.co/api/people/8/", "https://swapi.co/api/people/9/", "https://swapi.co/api/people/10/", "https://swapi.co/api/people/12/", "https://swapi.co/api/people/13/", "https://swapi.co/api/people/14/", "https://swapi.co/api/people/15/", "https://swapi.co/api/people/16/", "https://swapi.co/api/people/18/", "https://swapi.co/api/people/19/", "https://swapi.co/api/people/81/" ], "created": "2014-12-10T14:23:31.880000Z", "director": "George Lucas", "edited": "2015-04-11T09:46:52.774897Z", "episode_id": 4, "opening_crawl": "It is a period of civil war.\r\nRebel spaceships, striking\r\nfrom a hidden base, have won\r\ntheir first victory against\r\nthe evil Galactic Empire.\r\n\r\nDuring the battle, Rebel\r\nspies managed to steal secret\r\nplans to the Empire's\r\nultimate weapon, the DEATH\r\nSTAR, an armored space\r\nstation with enough power\r\nto destroy an entire planet.\r\n\r\nPursued by the Empire's\r\nsinister agents, Princess\r\nLeia races home aboard her\r\nstarship, custodian of the\r\nstolen plans that can save her\r\npeople and restore\r\nfreedom to the galaxy....", "planets": [ "https://swapi.co/api/planets/2/", "https://swapi.co/api/planets/3/", "https://swapi.co/api/planets/1/" ], "producer": "Gary Kurtz, Rick McCallum", "release_date": "1977-05-25", "species": [ "https://swapi.co/api/species/5/", "https://swapi.co/api/species/3/", "https://swapi.co/api/species/2/", "https://swapi.co/api/species/1/", "https://swapi.co/api/species/4/" ], "starships": [ "https://swapi.co/api/starships/2/", "https://swapi.co/api/starships/3/", "https://swapi.co/api/starships/5/", "https://swapi.co/api/starships/9/", "https://swapi.co/api/starships/10/", "https://swapi.co/api/starships/11/", "https://swapi.co/api/starships/12/", "https://swapi.co/api/starships/13/" ], "title": "A New Hope", "url": "https://swapi.co/api/films/1/", "vehicles": [ "https://swapi.co/api/vehicles/4/", "https://swapi.co/api/vehicles/6/", "https://swapi.co/api/vehicles/7/", "https://swapi.co/api/vehicles/8/" ] }, { "characters": [ "https://swapi.co/api/people/2/", "https://swapi.co/api/people/3/", "https://swapi.co/api/people/6/", "https://swapi.co/api/people/7/", "https://swapi.co/api/people/10/", "https://swapi.co/api/people/11/", "https://swapi.co/api/people/20/", "https://swapi.co/api/people/21/", "https://swapi.co/api/people/22/", "https://swapi.co/api/people/33/", "https://swapi.co/api/people/36/", "https://swapi.co/api/people/40/", "https://swapi.co/api/people/43/", "https://swapi.co/api/people/46/", "https://swapi.co/api/people/51/", "https://swapi.co/api/people/52/", "https://swapi.co/api/people/53/", "https://swapi.co/api/people/58/", "https://swapi.co/api/people/59/", "https://swapi.co/api/people/60/", "https://swapi.co/api/people/61/", "https://swapi.co/api/people/62/", "https://swapi.co/api/people/63/", "https://swapi.co/api/people/64/", "https://swapi.co/api/people/65/", "https://swapi.co/api/people/66/", "https://swapi.co/api/people/67/", "https://swapi.co/api/people/68/",

Slide 24

Slide 24 text

$ http -b https://swapi.co/api/films/ { "count": 7, "next": null, "previous": null, "results": [ { "characters": [ /* urls */ ], "created": "2014-12-10T14:23:31.880000Z", "director": "George Lucas", "edited": "2015-04-11T09:46:52.774897Z", "episode_id": 4, "opening_crawl": “…”, "planets": [ /* urls */ ], "producer": "Gary Kurtz, Rick McCallum", "release_date": "1977-05-25", "species": [/* urls */ ], "starships": [ /* urls */ ], "title": "A New Hope", "url": "https://swapi.co/api/films/1/", "vehicles": [ /* urls */ ] },

Slide 25

Slide 25 text

struct Films: Decodable { let count: Int let results: [Film] } struct Film: Decodable { let title: String let episode_id: Int let url: URL }

Slide 26

Slide 26 text

let session = URLSession(configuration: .default) let filmsURL = URL(string: "https://swapi.co/api/films")! let request = URLRequest(url: filmsURL) let task = session.dataTask(with: request) { data, response, error in guard error == nil, let data = data else { return } let decoder = JSONDecoder() do { let films = try decoder.decode(Films.self, from: data) print(films) } catch let error { print(error) } } task.resume()

Slide 27

Slide 27 text

Films(count: 7, results: [__lldb_expr_54.Film(title: "A New Hope”, episode_id: 4, url: https://swapi.co/api/films/1/), __lldb_expr_54.Film(title: "Attack of the Clones", episode_id: 2, url: https://swapi.co/api/films/5/), __lldb_expr_54.Film(title: "The Phantom Menace", episode_id: 1, url: https://swapi.co/api/films/4/), __lldb_expr_54.Film(title: "Revenge of the Sith", episode_id: 3, url: https://swapi.co/api/films/6/), __lldb_expr_54.Film(title: "Return of the Jedi”, episode_id: 6, url: https://swapi.co/api/films/3/), __lldb_expr_54.Film(title: "The Empire Strikes Back”, episode_id: 5, url: https://swapi.co/api/films/2/), __lldb_expr_54.Film(title: "The Force Awakens”, episode_id: 7, url: https://swapi.co/api/films/7/) ])

Slide 28

Slide 28 text

struct Films: Decodable { let count: Int let results: [Film] } struct Film: Decodable { let title: String let episode_id: Int let url: URL }

Slide 29

Slide 29 text

struct Films: Decodable { let count: Int let results: [Film] } struct Film: Decodable { let title: String let episodeID: Int let url: URL enum CodingKeys: CodingKey, String { case title = "title" case episodeID = "episode_id" case url = "url" } }

Slide 30

Slide 30 text

Films(count: 7, results: [__lldb_expr_65.Film(title: "A New Hope”, episodeID: 4, url: https://swapi.co/api/films/1/), __lldb_expr_65.Film(title: "Attack of the Clones", episodeID: 2, url: https://swapi.co/api/films/5/), __lldb_expr_65.Film(title: "The Phantom Menace", episodeID: 1, url: https://swapi.co/api/films/4/), __lldb_expr_65.Film(title: "Revenge of the Sith”, episodeID: 3, url: https://swapi.co/api/films/6/), __lldb_expr_65.Film(title: "Return of the Jedi”, episodeID: 6, url: https://swapi.co/api/films/3/), __lldb_expr_65.Film(title: "The Empire Strikes Back”, episodeID: 5, url: https://swapi.co/api/films/2/), __lldb_expr_65.Film(title: "The Force Awakens”, episodeID: 7, url: https://swapi.co/api/films/7/) ])

Slide 31

Slide 31 text

struct Films: Decodable { let count: Int let results: [Film] } struct Film: Decodable { let title: String let episodeID: Int let url: URL enum CodingKeys: CodingKey, String { case title = "title" case episodeID = "episode_id" case url = "url" } }

Slide 32

Slide 32 text

let session = URLSession(configuration: .default) let filmsURL = URL(string: "https://swapi.co/api/films")! let request = URLRequest(url: filmsURL) let task = session.dataTask(with: request) { data, response, error in guard error == nil, let data = data else { return } let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase // New in Swift 4.1! do { let films = try decoder.decode(Films.self, from: data) print(films) } catch let error { print(error) } } task.resume()

Slide 33

Slide 33 text

struct Films: Decodable { let count: Int let results: [Film] } struct Film: Decodable { let title: String let episodeID: Int // Automatically mapped from “episode_id” field let url: URL }

Slide 34

Slide 34 text

Films(count: 7, results: [__lldb_expr_65.Film(title: "A New Hope”, episodeID: 4, url: https://swapi.co/api/films/1/), __lldb_expr_65.Film(title: "Attack of the Clones", episodeID: 2, url: https://swapi.co/api/films/5/), __lldb_expr_65.Film(title: "The Phantom Menace", episodeID: 1, url: https://swapi.co/api/films/4/), __lldb_expr_65.Film(title: "Revenge of the Sith”, episodeID: 3, url: https://swapi.co/api/films/6/), __lldb_expr_65.Film(title: "Return of the Jedi”, episodeID: 6, url: https://swapi.co/api/films/3/), __lldb_expr_65.Film(title: "The Empire Strikes Back”, episodeID: 5, url: https://swapi.co/api/films/2/), __lldb_expr_65.Film(title: "The Force Awakens”, episodeID: 7, url: https://swapi.co/api/films/7/) ])

Slide 35

Slide 35 text

KeyEncodingStrategy / KeyDecodingStrategy • .convertTo/FromSnakeCase • .useDefaultKeys • .custom(([CodingKey]) -> CodingKey)

Slide 36

Slide 36 text

$ http -b https://swapi.co/api/films/ { "count": 7, "next": null, "previous": null, "results": [ { "characters": [ /* urls */ ], "created": "2014-12-10T14:23:31.880000Z", "director": "George Lucas", "edited": "2015-04-11T09:46:52.774897Z", "episode_id": 4, "opening_crawl": “…”, "planets": [ /* urls */ ], "producer": "Gary Kurtz, Rick McCallum", "release_date": "1977-05-25", "species": [/* urls */ ], "starships": [ /* urls */ ], "title": "A New Hope", "url": "https://swapi.co/api/films/1/", "vehicles": [ /* urls */ ] },

Slide 37

Slide 37 text

struct Films: Decodable { let count: Int let results: [Film] } struct Film: Decodable { let title: String let episode_id: Int let release_date: Date let url: URL }

Slide 38

Slide 38 text

typeMismatch(Swift.Double, Swift.DecodingError.Context(codingPath: [__lldb_expr_9.Films.(CodingKeys in _08D7FDC16B0B899A9D8E5E3183FA3D98).results, Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 0", intValue: Optional(0)), __lldb_expr_9.Film.(CodingKeys in _08D7FDC16B0B899A9D8E5E3183FA3D98).release_date], debugDescription: "Expected to decode Double but found a string/data instead.", underlyingError: nil))

Slide 39

Slide 39 text

let session = URLSession(configuration: .default) let filmsURL = URL(string: "https://swapi.co/api/films")! let request = URLRequest(url: filmsURL) let task = session.dataTask(with: request) { data, response, error in guard error == nil, let data = data else { return } let decoder = JSONDecoder() let formatter = DateFormatter() formatter.dateFormat = "yyyy-mm-dd" decoder.dateDecodingStrategy = .formatted(formatter) do { let films = try decoder.decode(Films.self, from: data) print(films) } catch let error { print(error) } } task.resume()

Slide 40

Slide 40 text

Films(count: 7, results: [__lldb_expr_65.Film(title: "A New Hope”, episodeID: 4, release_date: 1977-01-25 00:05:00 +0000 url: https://swapi.co/api/films/1/), __lldb_expr_65.Film(title: "Attack of the Clones", episodeID: 2, release_date: 2002-01-16 00:05:00 +0000 url: https://swapi.co/api/films/5/), __lldb_expr_65.Film(title: "The Phantom Menace", episodeID: 1, release_date: 1999-01-19 00:05:00 +0000 url: https://swapi.co/api/films/4/), __lldb_expr_65.Film(title: "Revenge of the Sith”, episodeID: 3, release_date: 2005-01-19 00:05:00 +0000 url: https://swapi.co/api/films/6/), __lldb_expr_65.Film(title: "Return of the Jedi”, episodeID: 6, release_date: 1983-01-25 00:05:00 +0000 url: https://swapi.co/api/films/3/), __lldb_expr_65.Film(title: "The Empire Strikes Back”, episodeID: 5, release_date: 1980-01-17 00:05:00 +0000 url: https://swapi.co/api/films/2/), __lldb_expr_65.Film(title: "The Force Awakens”, episodeID: 7, release_date: 2015-01-11 00:12:00 +0000 url: https://swapi.co/api/films/7/) ])

Slide 41

Slide 41 text

DateEncodingStrategy / DateDecodingStrategy • .deferredToDate • .iso8601 • .millisecondsSince1970 • .secondsSince1970 • .formatted(DateFormatter) • .custom((Date, Encoder) throws -> Void)

Slide 42

Slide 42 text

DataEncodingStrategy / DataDecodingStrategy • .deferredToData • .base64 • .custom((Data, Encoder) throws -> Void)

Slide 43

Slide 43 text

NonConformingFloatEncodingStrategy / NonConformingFloatDecodingStrategy • .throw • .convertTo/FromString(positiveInfinity: String, negativeInfinity: String, nan: String)

Slide 44

Slide 44 text

struct Person: Decodable { let name: String let height: Float } decoder.nonConformingFloatDecodingStrategy = .convertFromString( positiveInfinity: "+Infinity", negativeInfinity: "-Infinity", nan: "NaN") let person = try! decoder.decode(Person.self, from: json) print(person)

Slide 45

Slide 45 text

Fullstack
 Development

Slide 46

Slide 46 text

{ "name": "", "photo": "", "dateOfBirth": "" } { "name": "", "photo": "", "dateOfBirth": { "year": "month": "day": } } struct Profile { var name: String var photo: Data var dateOfBirth: Date } Swift var profile: [String : Any] Swift KITURA

Slide 47

Slide 47 text

struct Profile { var name: String var photo: Data var dateOfBirth: Date } Swift

Slide 48

Slide 48 text

struct Profile { var name: String var photo: Data var dateOfBirth: Date } Swift Swift KITURA

Slide 49

Slide 49 text

struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } Swift Swift KITURA

Slide 50

Slide 50 text

struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } Swift Swift struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } KITURA

Slide 51

Slide 51 text

struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } Swift Swift struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } let encoder = JSONEncoder() let data = try encoder.encode(profile) KITURA

Slide 52

Slide 52 text

struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } Swift Swift struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } let decoder = JSONDecoder() let person = try decoder.decode(Person.self, from: jsonData) KITURA

Slide 53

Slide 53 text

{ "name": "", "photo": "", "dateOfBirth": "" } { "name": "", "photo": "", "dateOfBirth": "" } struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } Swift Swift struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } KITURA

Slide 54

Slide 54 text

Body Data

Slide 55

Slide 55 text

{ "name": "John Doe", "photo": "jbbkj362", "dateOfBirth": "06-06-1980“ }

Slide 56

Slide 56 text

struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date }

Slide 57

Slide 57 text

struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } router.get("/profile", handler: getProfiles)

Slide 58

Slide 58 text

struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } router.get("/profile", handler: getProfiles) func getProfiles(request: RouterRequest, response: RouterResponse, next: () -> Void) -> Void { }

Slide 59

Slide 59 text

struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } router.get("/profile", handler: getProfiles) func getProfiles(request: RouterRequest, response: RouterResponse, next: () -> Void) -> Void { var profile = request.read(as: Profile.Type) next() }

Slide 60

Slide 60 text

struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } router.get("/profile", handler: getProfiles) func getProfiles(respondWith: @escaping ([Profile]?, Error?) -> Void) -> Void { }

Slide 61

Slide 61 text

struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } router.get("/profile", handler: getProfiles) func getProfiles(respondWith: @escaping ([Profile]?, Error?) -> Void) -> Void { ... respondWith(profiles, nil) }

Slide 62

Slide 62 text

struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } router.get("/profile", handler: getProfiles) router.post("/profile", handler: addProfile) func getProfiles(respondWith: @escaping ([Profile]?, Error?) -> Void) -> Void { ... respondWith(profiles, nil) }

Slide 63

Slide 63 text

struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } router.get("/profile", handler: getProfiles) router.post("/profile", handler: addProfile) func getProfiles(respondWith: @escaping ([Profile]?, Error?) -> Void) -> Void { ... respondWith(profiles, nil) } func addProfile(profile: Profile, respondWith: @escaping (Profile?, Error?) -> Void) -> Void { ... respondWith(profile, nil) }

Slide 64

Slide 64 text

guard let backend = KituraKit(baseURL: "http://localhost:8080") else { print("Error creating KituraKit client") return } backend.get("/profile") { (profiles: [Profile]?, error: RequestError?) in ... } KITURAKIT

Slide 65

Slide 65 text

Query Parameters

Slide 66

Slide 66 text

GET: www.example.com/profile?name=“John Doe”#

Slide 67

Slide 67 text

{ "name": "John Doe", "photo": "jbbkj362", "dateOfBirth": "06-06-1980“ }

Slide 68

Slide 68 text

{"name": "John Doe","photo": "jbbkj362","dateOfBirth": "06-06-1980"}

Slide 69

Slide 69 text

?name="John Doe"#

Slide 70

Slide 70 text

struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } router.get("/profile", handler: getProfiles) func getProfiles(respondWith: @escaping ([Profile]?, Error?) -> Void) -> Void { ... respondWith(profiles, nil) }

Slide 71

Slide 71 text

struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } struct Query: QueryParams { var name: String } router.get("/profile", handler: getProfiles) func getProfiles(respondWith: @escaping ([Profile]?, Error?) -> Void) -> Void { ... respondWith(profiles, nil) }

Slide 72

Slide 72 text

struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } struct Query: QueryParams { var name: String } router.get("/profile", handler: getProfiles) func getProfiles(query: Query, respondWith: @escaping ([Profile]?, Error?) -> Void) -> Void { ... respondWith(profiles, nil) }

Slide 73

Slide 73 text

struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } struct Query: QueryParams { var name: String } router.get("/profile", handler: getProfiles) func getProfiles(query: Query, respondWith: @escaping ([Profile]?, Error?) -> Void) -> Void { ... respondWith(profiles.filter{ ($0.name == query.name), nil) }

Slide 74

Slide 74 text

What Else?

Slide 75

Slide 75 text

SELECT * from Profiles

Slide 76

Slide 76 text

name,, photo, dateOfBirth, "John Doe", "jbbkj362", "06-06-1980",

Slide 77

Slide 77 text

{name:”John Doe”,photo:”jbbkj362”,dateOfBirth:”06-06-1980”}

Slide 78

Slide 78 text

struct Profile: Codable { var name: String var photo: Data var dateOfBirth: Date } router.get("/profile", handler: getProfiles) func getProfiles(respondWith: @escaping ([Profile]?, Error?) -> Void) -> Void { ... respondWith(profiles, nil) }

Slide 79

Slide 79 text

struct Profile: Model { var name: String var photo: Data var dateOfBirth: Date } router.get("/profile", handler: getProfiles) func getProfiles(completion: @escaping ([Profile]?, Error?) -> Void) -> Void { Profile.getAll(completion) }

Slide 80

Slide 80 text

SELECT * from Profiles WHERE name = “John Doe”

Slide 81

Slide 81 text

struct Profile: Model { var name: String var photo: Data var dateOfBirth: Date } router.get("/profile", handler: getProfiles) func getProfiles(completion: @escaping ([Profile]?, Error?) -> Void) -> Void { Profile.getAll(completion) }

Slide 82

Slide 82 text

struct Profile: Model { var name: String var photo: Data var dateOfBirth: Date } struct Query: QueryParams { var name: String } router.get("/profile", handler: getProfiles) func getProfiles(completion: @escaping ([Profile]?, Error?) -> Void) -> Void { Profile.getAll(completion) }

Slide 83

Slide 83 text

struct Profile: Model { var name: String var photo: Data var dateOfBirth: Date } struct Query: QueryParams { var name: String } router.get("/profile", handler: getProfiles) func getProfiles(query: Query, completion: @escaping ([Profile]?, Error?) -> Void) -> Void { Profile.getAll(matching: query, completion) }

Slide 84

Slide 84 text

Codable

Slide 85

Slide 85 text

Dynamic Swift

Slide 86

Slide 86 text

Thanks! www.kitura.io github.com/ibm-swift/kitura slack.kitura.io We’d love to see you at the IBM booth here at iOSCon!