Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Same language, different platform - searching for synergy between iOS and Vapor

Same language, different platform - searching for synergy between iOS and Vapor

Being able to share code between an iOS app and a backend running server-side Swift was one of the first things I thought about when Swift went open source. It’s been a common argument for choosing a server-side Swift framework ever since, although there’s been little focus on what is actually possible. With this talk I will dive into the current possibilities and limitations for sharing code between iOS and Vapor and I will look ahead and discus how this might change in the future.

Steffen D. Sommer

September 20, 2018
Tweet

More Decks by Steffen D. Sommer

Other Decks in Programming

Transcript

  1. Same language,
    different platform
    Searching for synergy between iOS and Vapor
    SwiftConf 2018

    View Slide

  2. !

    View Slide

  3. • " Head of Vapor Development at Nodes
    • # Background in iOS development
    • $ Full time Vapor developer since January 2017
    • % ~50 customer projects and ~40 packages
    • & On the hunt for synergy..
    @steffendsommer

    View Slide

  4. '

    View Slide

  5. View Slide

  6. “Platform support for all Apple platforms as well as
    Linux”

    View Slide

  7. View Slide

  8. View Slide

  9. • ( A web framework built in Swift
    • ) Official support for SQLite, MySQL, PostgreSQL and Redis
    • * Built on top of SwiftNIO

    View Slide

  10. import Vapor
    let app = try Application()
    let router = try app.make(Router.self)
    router.get("hello") { req in
    return "Hello, world."
    }
    try app.run()

    View Slide

  11. • + Strong, safe and modern language
    • , Fast and has a low memory footprint (aka. -)
    • ♻ Potential synergy between iOS and Vapor
    • / Tech awesomeness

    View Slide

  12. • * Modern and Swifty API’s
    • 0 Great community
    • 1 Growing eco system
    • 2 Most popular

    View Slide

  13. 3
    • 4 Everything is “new”
    • ( Tooling
    • ⚙ Foundation
    • 1 Eco system
    • 6 Resources

    View Slide

  14. View Slide

  15. –Wikipedia
    “Synergy is the creation of a whole that is greater than the
    simple sum of its parts.”

    View Slide

  16. View Slide


  17. # JSON
    Models Models
    Errors Errors
    Validation Validation
    Endpoints Endpoints

    View Slide


  18. # JSON
    Models Models
    Errors Errors
    Validation Validation
    Endpoints Endpoints

    View Slide


  19. #

    Models
    Errors
    Validation
    Endpoints
    JSON

    View Slide


  20. View Slide

  21. View Slide

  22. View Slide

  23. " 9

    View Slide

  24. " 9

    View Slide

  25. :

    View Slide

  26. View Slide

  27. ;

    View Slide

  28. #
    “Can I get an endpoint which return
    a list of subcategories?”
    “Yes.. it’s ready now”
    “Wait a sec, that endpoint gives
    me product categories?”
    “Oh.. so you want
    categories to be returned?”

    View Slide

  29. <

    View Slide

  30. • ⏱ To save time (write once, use twice)
    • "9 To share work-load
    • : To keep code in sync across platforms
    • ; To share knowledge about implementation
    • < To hide the data interchange format

    View Slide

  31. View Slide

  32. =

    View Slide

  33. final class Work: Codable {
    enum Kind: String, Codable {
    // ..
    }
    enum Framework: String, Codable {
    // ..
    }
    var id: Int?
    var company: String
    var companyLogoUrl: String
    var location: String
    var kind: Kind
    var framework: Framework
    var remoteAllowed: Bool
    var title: String
    var description: String
    var externalUrl: String
    var contactEmail: String
    var approvedAt: Date?
    var createdAt: Date?
    var updatedAt: Date?
    var deletedAt: Date?
    init(
    company: String,
    companyLogoUrl: String,
    location: String,
    kind: Kind,
    framework: Framework,
    remoteAllowed: Bool,
    title: String,
    description: String,
    externalUrl: String,
    contactEmail: String
    ) {
    // ..
    }
    }

    View Slide

  34. #
    struct Work: Codable {
    enum Kind: String, Codable {
    // ..
    }
    enum Framework: String, Codable {
    // ..
    }
    var id: Int?
    var company: String
    var companyLogoUrl: String
    var location: String
    var kind: Kind
    var framework: Framework
    var remoteAllowed: Bool
    var title: String
    var description: String
    var externalUrl: String
    var contactEmail: String
    var approvedAt: Date?
    var createdAt: Date?
    var updatedAt: Date?
    var deletedAt: Date?
    }

    View Slide


  35. public final class Work: Codable {
    public enum Kind: String, Codable {
    // ..
    }
    public enum Framework: String, Codable {
    // ..
    }
    public var id: Int?
    public var company: String
    public var companyLogoUrl: String
    public var location: String
    public var kind: Kind
    public var framework: Framework
    public var remoteAllowed: Bool
    public var title: String
    public var description: String
    public var externalUrl: String
    public var contactEmail: String
    public var approvedAt: Date?
    public var createdAt: Date?
    public var updatedAt: Date?
    public var deletedAt: Date?
    public init(
    company: String,
    companyLogoUrl: String,
    location: String,
    kind: Kind,
    framework: Framework,
    remoteAllowed: Bool,
    title: String,
    description: String,
    externalUrl: String,
    contactEmail: String
    ) {
    // ..
    }
    }

    View Slide


  36. View Slide

  37. enum WorkError: Error {
    case noWorkAvailable
    }
    extension WorkError: AbortError {
    var identifier: String {
    return "noWork"
    }
    var status: HTTPResponseStatus {
    return .custom(code: 499, reasonPhrase: "No work available")
    }
    var reason: String {
    return "Unfortunately, no work available at this moment."
    }
    }

    View Slide

  38. #
    enum WorkError: Error {
    case noWork
    }
    extension WorkError {
    init?(from statusCode: Int) {
    switch statusCode {
    case 499: self = .noWork
    default: return nil
    }
    }
    }

    View Slide


  39. enum APIError: Error {
    case noWorkAvailable
    var httpCode: UInt {
    switch self {
    case .noWorkAvailable: return 499
    }
    }
    init?(from httpCode: UInt) {
    switch httpCode {
    case 499: self = .noWorkAvailable
    default: return nil
    }
    }
    }

    View Slide

  40. ?

    View Slide

  41. public func routes(_ router: Router) throws {
    let api = router.grouped("api")
    let workAPIController = APIWorkController()
    api.get("/work", use: workAPIController.workList)
    api.get("/work", Work.parameter, use: workAPIController.work)
    }
    enum EnvironmentKey {
    enum Project {
    static let url = "PROJECT_URL"
    }
    }

    View Slide

  42. #
    struct WorkAPIService {
    enum Environment: String {
    case production = "http://serversideswift.work"
    }
    let environment: Environment
    func work(complete: @escaping ([Work]) -> ()) {
    sendRequest(to: "\(environment.rawValue)/api/work/") { (work: [Work]) in
    complete(work)
    }
    }
    func work(for id: Int, complete: @escaping (Work) -> ()) {
    sendRequest(to: "\(environment.rawValue)/api/work/\(id)") { (work: Work) in
    complete(work)
    }
    }
    }

    View Slide


  43. public enum API {
    public enum Environment: String {
    case production = "http://serversideswift.work"
    func url(to endpoint: Endpoint) -> String {
    return self.rawValue + endpoint.path
    }
    }
    public enum Endpoint {
    case workList
    case work(Int)
    var path: String {
    switch self {
    case .workList: return "/api/work"
    case .work(let id): return "/api/work/\(id)"
    }
    }
    }
    }

    View Slide


  44. View Slide

  45. final class APIWorkController {
    func workList(_ req: Request) throws -> Future<[Work]> {
    return Work
    .query(on: req)
    .filter(\.approvedAt != nil)
    .sort(\.approvedAt, .descending)
    .all()
    .thenThrowing { items in
    guard items.count > 0 else {
    throw WorkError.noWorkAvailable
    }
    return items
    }
    }
    func work(_ req: Request) throws -> Future {
    return try req.parameters.next(Work.self)
    }
    }

    View Slide

  46. #
    struct WorkAPIService {
    let environment: API.Environment
    func work(complete: @escaping ([Work]) -> ()) {
    sendRequest(to: environment.url(to: .workList)) { (work: [Work]) in
    complete(work)
    }
    }
    func work(for id: Int, complete: @escaping (Work) -> ()) {
    sendRequest(to: environment.url(to: .work(id))) { (work: Work) in
    complete(work)
    }
    }
    func sendRequest(to url: String, complete: @escaping (C) -> ()) {
    guard let url = URL(string: url) else { return }
    let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
    guard
    let dataResponse = data,
    let urlResponse = response as? HTTPURLResponse,
    error == nil
    else { return }
    do {
    let decoder = JSONDecoder()
    decoder.dateDecodingStrategy = .iso8601
    let results = try decoder.decode(C.self, from: dataResponse)
    complete(results)
    } catch let parsingError {
    print("Error", parsingError)
    }
    }
    task.resume()
    }
    }

    View Slide


  47. public protocol HTTPClient {
    func sendRequest(to url: String, complete: @escaping (C) -> ())
    }
    public struct APIClient {
    internal let client: HTTPClient
    internal let environment: API.Environment
    public init(client: HTTPClient, environment: API.Environment) {
    self.client = client
    self.environment = environment
    }
    public func work(complete: @escaping ([Work]) -> ()) {
    client.sendRequest(to: environment.url(to: .workList)) { (work: [Work]) in
    complete(work)
    }
    }
    public func work(for id: Int, complete: @escaping (Work) -> ()) {
    client.sendRequest(to: environment.url(to: .work(id))) { (work: Work) in
    complete(work)
    }
    }
    }

    View Slide

  48. A

    View Slide

  49. • = Models
    • ⚠ Errors
    • ✅ Validation
    • ? Endpoints & Environments
    • C Business logic
    • D Styling
    • E Test code (e.g. mocks)
    • F Frameworks used in both places
    • ☂ A framework that wraps the endpoints

    View Slide



  50. #
    my-app-ios my-app-vapor
    my-app-shared

    View Slide

  51. View Slide

  52. G
    • # Copy files from shared repo into your project
    • $ Copy files from shared repo into your project

    View Slide

  53. H
    • # Add submodule and drag files into project
    • $ Add submodule and re-generate Xcode project file

    View Slide

  54. <
    • # Use SPM to pull down dependences and use this trick*
    to generate an iOS project. Alternatively use SwiftXcode.
    • $ Add the shared repo as a dependency
    * https://www.ralfebert.de/ios-examples/xcode/ios-dependency-management-with-swift-package-manager/

    View Slide

  55. I
    • # Check in Xcode project on shared repo and add the
    dependency to your Cartfile
    • $ N/A

    View Slide

  56. • G Copy and paste
    • H Git submodules
    • < Swift Package Manager
    • I Carthage
    • ☕ CocoaPods

    View Slide

  57. • K A change most likely requires platform-specific updates
    • L Both platforms would still have to sync and deploy/release
    • ☁ Code might depend on something that doesn’t run on the
    other platform
    • M Potential overhead in abstracting code that can be shared
    • N Not great until SPM gets integrated in Xcode
    3

    View Slide

  58. O

    View Slide

  59. • < Swift Package Manager integrated in Xcode
    • P Fluent on iOS
    • $ Vapor on iOS
    • Q Examples/resources/tools/frameworks
    • R Swift on Android

    View Slide

  60. • $ https://docs.vapor.codes
    • S https://discord.gg/vapor
    • T https://www.serversideswift.info
    • U Tim Condon - Getting Started with Server Side Swift and
    Vapor (Codemobile 2018)
    • 9 https://www.serversideswift.work

    View Slide

  61. V
    Any questions?
    @steffendsommer

    View Slide