Slide 1

Slide 1 text

SCRIPTING IN SWIFT FOR A TESTABLE BUILD IOS CONF SG | SINGAPORE | JANUARY 2020 ELLEN SHAPIRO | @DESIGNATEDNERD | APOLLOGRAPHQL.COM

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

SINGAPORE NOW @CARROT_App

Slide 4

Slide 4 text

MADISON NOW @CARROT_App

Slide 5

Slide 5 text

YOUR IOS PROJECT HAS A NEST OF BASH SCRIPTS @DesignatedNerd

Slide 6

Slide 6 text

YOUR IOS PROJECT HAS A NEST OF BASH SCRIPTS (IT'S OKAY, MY PROJECTS DO TOO) @DesignatedNerd

Slide 7

Slide 7 text

@DesignatedNerd

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

SPOILER: THERE IS A BETTER WAY @DesignatedNerd

Slide 10

Slide 10 text

SCRIPTING WITH ✨ SWIFT @DesignatedNerd

Slide 11

Slide 11 text

@DesignatedNerd

Slide 12

Slide 12 text

! HOW DOES APOLLO WORK? @DesignatedNerd

Slide 13

Slide 13 text

! HOW DOES APOLLO WORK? (THE SHORT VERSION) @DesignatedNerd

Slide 14

Slide 14 text

GraphQL @DesignatedNerd

Slide 15

Slide 15 text

SCHEMA + QUERIES = CODE @DesignatedNerd

Slide 16

Slide 16 text

SCHEMA + QUERIES = CODE @DesignatedNerd

Slide 17

Slide 17 text

SCHEMA + QUERIES = CODE WHAT IS POSSIBLE? @DesignatedNerd

Slide 18

Slide 18 text

SCHEMA + QUERIES = CODE @DesignatedNerd

Slide 19

Slide 19 text

SCHEMA + QUERIES = CODE WHAT AM I ASKING FOR? @DesignatedNerd

Slide 20

Slide 20 text

SCHEMA + QUERIES = CODE @DesignatedNerd

Slide 21

Slide 21 text

SCHEMA + QUERIES = CODE IS WHAT I'M ASKING FOR POSSIBLE? @DesignatedNerd

Slide 22

Slide 22 text

! @DesignatedNerd

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

! @DesignatedNerd

Slide 25

Slide 25 text

SCHEMA + QUERIES = CODE @DesignatedNerd

Slide 26

Slide 26 text

SCHEMA + QUERIES = CODE HAVE SOME TYPE-SAFE CODE! @DesignatedNerd

Slide 27

Slide 27 text

JAVASCRIPT + NODE @DesignatedNerd

Slide 28

Slide 28 text

TYPESCRIPT + NODE @DesignatedNerd

Slide 29

Slide 29 text

via Webcomic Name and Alex Norris

Slide 30

Slide 30 text

npm = @DesignatedNerd

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

HOW DO WE MAKE THIS BETTER? @DesignatedNerd

Slide 33

Slide 33 text

!!!! @DesignatedNerd

Slide 34

Slide 34 text

HOW DO WE MAKE THIS LESS TERRIBLE AS QUICKLY AS POSSIBLE? @DesignatedNerd

Slide 35

Slide 35 text

BUNDLING EVERYTHING @DesignatedNerd

Slide 36

Slide 36 text

BUNDLING EVERYTHING A SMALLER @DesignatedNerd

Slide 37

Slide 37 text

BEFORE LOTS OF BASH YOU HAD TO WRITE @DesignatedNerd

Slide 38

Slide 38 text

AFTER LOTS OF BASH I HAD TO WRITE @DesignatedNerd

Slide 39

Slide 39 text

@DesignatedNerd

Slide 40

Slide 40 text

I HATE BASH @DesignatedNerd

Slide 41

Slide 41 text

I HATE SUCK AT BASH @DesignatedNerd

Slide 42

Slide 42 text

TESTING AND DEBUGGING BASH: ¯\_(ϑ)_/¯ @DesignatedNerd

Slide 43

Slide 43 text

echo "Variable: ${VARIABLE}" @DesignatedNerd

Slide 44

Slide 44 text

FIGURING OUT WHAT WENT WRONG IN BASH: ¯\_(ϑ)_/¯ @DesignatedNerd

Slide 45

Slide 45 text

! @DesignatedNerd

Slide 46

Slide 46 text

DON'T IT ALWAYS SEEM TO GO THAT YOU DON'T KNOW WHAT YOU GOT 'TIL IT'S GONE - JONI MITCHELL @DesignatedNerd

Slide 47

Slide 47 text

LET'S DO THIS! @DesignatedNerd

Slide 48

Slide 48 text

1. BUILD A SWIFT FRAMEWORK THAT DOES WHAT YOUR SCRIPT CURRENTLY DOES @DesignatedNerd

Slide 49

Slide 49 text

IDENTIFY WHAT YOUR SCRIPT DOES @DesignatedNerd

Slide 50

Slide 50 text

IDENTIFY WHAT YOUR SCRIPT DOES (THIS SOUNDS MUCH EASIER THAN IT IS) @DesignatedNerd

Slide 51

Slide 51 text

run_bundled_codegen.sh @DesignatedNerd

Slide 52

Slide 52 text

WHAT APOLLO'S SCRIPT DOES @DesignatedNerd

Slide 53

Slide 53 text

WHAT APOLLO'S SCRIPT DOES Is there a zipped version of the CLI? @DesignatedNerd

Slide 54

Slide 54 text

WHAT APOLLO'S SCRIPT DOES Is there a zipped version of the CLI? NO @DesignatedNerd

Slide 55

Slide 55 text

WHAT APOLLO'S SCRIPT DOES Is there a zipped version of the CLI? NO Download and unzip current version of CLI. @DesignatedNerd

Slide 56

Slide 56 text

WHAT APOLLO'S SCRIPT DOES Is there a zipped version of the CLI? YES @DesignatedNerd

Slide 57

Slide 57 text

WHAT APOLLO'S SCRIPT DOES Is there a zipped version of the CLI? YES Does the zipped version of the CLI have the correct hash for this version of the iOS SDK? @DesignatedNerd

Slide 58

Slide 58 text

WHAT APOLLO'S SCRIPT DOES Is there a zipped version of the CLI? YES Does the zipped version of the CLI have the correct hash for this version of the iOS SDK? NO @DesignatedNerd

Slide 59

Slide 59 text

WHAT APOLLO'S SCRIPT DOES Is there a zipped version of the CLI? YES Does the zipped version of the CLI have the correct hash for this version of the iOS SDK? NO Download and unzip current version of CLI. @DesignatedNerd

Slide 60

Slide 60 text

WHAT APOLLO'S SCRIPT DOES Is there a zipped version of the CLI? YES Does the zipped version of the CLI have the correct hash for this version of the iOS SDK? YES @DesignatedNerd

Slide 61

Slide 61 text

WHAT APOLLO'S SCRIPT DOES Is there a zipped version of the CLI? YES Does the zipped version of the CLI have the correct hash for this version of the iOS SDK? YES Unzip if needed @DesignatedNerd

Slide 62

Slide 62 text

RUN THE CLI WITH THE PASSED IN INFORMATION ⬇ GENERATE CODE @DesignatedNerd

Slide 63

Slide 63 text

codegen:generate --target=swift --localSchemaFile="schema.json" --includes=./**/*.graphql --operationIdsPath=operationIdsPath.json --suppressSwiftMultilineStringLiterals ⬇ ApolloCodegenOptions @DesignatedNerd

Slide 64

Slide 64 text

if [ -f "${ZIP_FILE}" ] ⬇ FileManager @DesignatedNerd

Slide 65

Slide 65 text

curl ⬇ URLSession @DesignatedNerd

Slide 66

Slide 66 text

/usr/bin/shasum ⬇ CommonCrypto @DesignatedNerd

Slide 67

Slide 67 text

SEPARATION OF CONCERNS @DesignatedNerd

Slide 68

Slide 68 text

! DEPENDENCY INJECTION @DesignatedNerd

Slide 69

Slide 69 text

CODE COVERAGE @DesignatedNerd

Slide 70

Slide 70 text

@DesignatedNerd

Slide 71

Slide 71 text

@DesignatedNerd

Slide 72

Slide 72 text

@DesignatedNerd

Slide 73

Slide 73 text

! BEST PRACTICES, DUH @DesignatedNerd

Slide 74

Slide 74 text

YOUR SCRIPTS DESERVE TO BE FIRST-CLASS CITIZENS @DesignatedNerd

Slide 75

Slide 75 text

IF YOU CAN'T RELEASE YOUR APP WITHOUT IT TEST IT @DesignatedNerd

Slide 76

Slide 76 text

IF YOU CAN'T RELEASE YOUR APP WITHOUT IT TEST IT (AND IT'S A LOT EASIER TO TEST IT IN SWIFT) @DesignatedNerd

Slide 77

Slide 77 text

2. USE THE LIBRARY IN A SPM EXECUTABLE @DesignatedNerd

Slide 78

Slide 78 text

#! /usr/bin/env xcrun swift -F ../build/Debug @DesignatedNerd

Slide 79

Slide 79 text

chmod +x main.swift @DesignatedNerd

Slide 80

Slide 80 text

@DesignatedNerd

Slide 81

Slide 81 text

@DesignatedNerd

Slide 82

Slide 82 text

@DesignatedNerd

Slide 83

Slide 83 text

swift run @DesignatedNerd

Slide 84

Slide 84 text

DON'T DO THIS @DesignatedNerd

Slide 85

Slide 85 text

CREATE IT WITH SWIFT PACKAGE MANAGER $ mkdir Codegen $ cd Codegen $ swift package init --type executable @DesignatedNerd

Slide 86

Slide 86 text

Package.swift // swift-tools-version:5.1 import PackageDescription let package = Package( name: "Codegen", products: [ .executable(name: "Codegen", targets: ["Codegen"]), ], dependencies: [ .package(url: "https://github.com/apollographql/apollo-ios.git", .branch("swift-codegen")) ], targets: [ .target(name: "Codegen", dependencies: ["ApolloCodegenLib"]), .testTarget(name: "CodegenTests", dependencies: ["Codegen"]), ] ) @DesignatedNerd

Slide 87

Slide 87 text

Package.swift // swift-tools-version:5.1 import PackageDescription let package = Package( name: "Codegen", products: [ .executable(name: "Codegen", targets: ["Codegen"]), ], dependencies: [ .package(url: "https://github.com/apollographql/apollo-ios.git", .branch("swift-codegen")) ], targets: [ .target(name: "Codegen", dependencies: ["ApolloCodegenLib"]), .testTarget(name: "CodegenTests", dependencies: ["Codegen"]), ] ) @DesignatedNerd

Slide 88

Slide 88 text

Package.swift // swift-tools-version:5.1 import PackageDescription let package = Package( name: "Codegen", products: [ .executable(name: "Codegen", targets: ["Codegen"]), ], dependencies: [ .package(url: "https://github.com/apollographql/apollo-ios.git", .branch("swift-codegen")) ], targets: [ .target(name: "Codegen", dependencies: ["ApolloCodegenLib"]), .testTarget(name: "CodegenTests", dependencies: ["Codegen"]), ] ) @DesignatedNerd

Slide 89

Slide 89 text

Package.swift // swift-tools-version:5.1 import PackageDescription let package = Package( name: "Codegen", products: [ .executable(name: "Codegen", targets: ["Codegen"]), ], dependencies: [ .package(url: "https://github.com/apollographql/apollo-ios.git", .branch("swift-codegen")) ], targets: [ .target(name: "Codegen", dependencies: ["ApolloCodegenLib"]), .testTarget(name: "CodegenTests", dependencies: ["Codegen"]), ] ) @DesignatedNerd

Slide 90

Slide 90 text

Package.swift // swift-tools-version:5.1 import PackageDescription let package = Package( name: "Codegen", products: [ .executable(name: "Codegen", targets: ["Codegen"]), ], dependencies: [ .package(url: "https://github.com/apollographql/apollo-ios.git", .branch("swift-codegen")) ], targets: [ .target(name: "Codegen", dependencies: ["ApolloCodegenLib"]), .testTarget(name: "CodegenTests", dependencies: ["Codegen"]), ] ) @DesignatedNerd

Slide 91

Slide 91 text

Package.swift // swift-tools-version:5.1 import PackageDescription let package = Package( name: "Codegen", products: [ .executable(name: "Codegen", targets: ["Codegen"]), ], dependencies: [ .package(url: "https://github.com/apollographql/apollo-ios.git", .branch("swift-codegen")) ], targets: [ .target(name: "Codegen", dependencies: ["ApolloCodegenLib"]), .testTarget(name: "CodegenTests", dependencies: ["Codegen"]), ] ) @DesignatedNerd

Slide 92

Slide 92 text

@DesignatedNerd

Slide 93

Slide 93 text

$SRCROOT @DesignatedNerd

Slide 94

Slide 94 text

@DesignatedNerd

Slide 95

Slide 95 text

main.swift print("Hello, world!") @DesignatedNerd

Slide 96

Slide 96 text

main.swift import Foundation enum MyCodegenError: Error { case sourceRootNotProvided case sourceRootNotADirectory case targetDoesntExist } guard let sourceRootPath = ProcessInfo.processInfo.environment["SRCROOT"] else { throw MyCodegenError.sourceRootNotProvided } @DesignatedNerd

Slide 97

Slide 97 text

main.swift import Foundation import ApolloCodegenLib enum MyCodegenError: Error { case sourceRootNotProvided case sourceRootNotADirectory case targetDoesntExist } guard let sourceRootPath = ProcessInfo.processInfo.environment["SRCROOT"] else { throw MyCodegenError.sourceRootNotProvided } guard FileManager.default.apollo_folderExists(at: sourceRootPath) else { throw MyCodegenError.sourceRootNotADirectory } @DesignatedNerd

Slide 98

Slide 98 text

main.swift (pt. 2) let applicationTargetFolderURL = sourceRootURL .appendingPathComponent("SwiftScriptTestSPM") guard FileManager.default.apollo_folderExists(at: applicationTarget) else { throw MyCodegenError.targetDoesntExist } let scriptFolderURL = sourceRootURL.appendingPathComponent("scripts") let options = ApolloCodegenOptions(targetRootURL: applicationTargetFolderURL) do { try ApolloCodegen.run(from: applicationTargetFolderURL, with: scriptsFolderURL, options: options) } catch { CodegenLogger.log("ERROR: \(error.localizedDescription)") exit(1) } @DesignatedNerd

Slide 99

Slide 99 text

main.swift (pt. 2) let applicationTargetFolderURL = sourceRootURL .appendingPathComponent("SwiftScriptTestSPM") guard FileManager.default.apollo_folderExists(at: applicationTarget) else { throw MyCodegenError.targetDoesntExist } let scriptFolderURL = sourceRootURL.appendingPathComponent("scripts") let options = ApolloCodegenOptions(targetRootURL: applicationTargetFolderURL) do { try ApolloCodegen.run(from: applicationTargetFolderURL, with: scriptsFolderURL, options: options) } catch { CodegenLogger.log("ERROR: \(error.localizedDescription)") exit(1) } @DesignatedNerd

Slide 100

Slide 100 text

main.swift (pt. 2) let applicationTargetFolderURL = sourceRootURL .appendingPathComponent("SwiftScriptTestSPM") guard FileManager.default.apollo_folderExists(at: applicationTarget) else { throw MyCodegenError.targetDoesntExist } let scriptFolderURL = sourceRootURL.appendingPathComponent("scripts") let options = ApolloCodegenOptions(targetRootURL: applicationTargetFolderURL) do { try ApolloCodegen.run(from: applicationTargetFolderURL, with: scriptsFolderURL, options: options) } catch { CodegenLogger.log("ERROR: \(error.localizedDescription)") exit(1) } @DesignatedNerd

Slide 101

Slide 101 text

main.swift (pt. 2) let applicationTargetFolderURL = sourceRootURL .appendingPathComponent("SwiftScriptTestSPM") guard FileManager.default.apollo_folderExists(at: applicationTarget) else { throw MyCodegenError.targetDoesntExist } let scriptFolderURL = sourceRootURL.appendingPathComponent("scripts") let options = ApolloCodegenOptions(targetRootURL: applicationTargetFolderURL) do { try ApolloCodegen.run(from: applicationTargetFolderURL, with: scriptsFolderURL, options: options) } catch { CodegenLogger.log("ERROR: \(error.localizedDescription)") exit(1) } @DesignatedNerd

Slide 102

Slide 102 text

main.swift (pt. 2) let applicationTargetFolderURL = sourceRootURL .appendingPathComponent("SwiftScriptTestSPM") guard FileManager.default.apollo_folderExists(at: applicationTarget) else { throw MyCodegenError.targetDoesntExist } let scriptFolderURL = sourceRootURL.appendingPathComponent("scripts") let options = ApolloCodegenOptions(targetRootURL: applicationTargetFolderURL) do { try ApolloCodegen.run(from: applicationTargetFolderURL, with: scriptsFolderURL, options: options) } catch { CodegenLogger.log("ERROR: \(error.localizedDescription)") exit(1) } @DesignatedNerd

Slide 103

Slide 103 text

IN YOUR APPLICATION PROJECT @DesignatedNerd

Slide 104

Slide 104 text

3. ! PROFIT! @DesignatedNerd

Slide 105

Slide 105 text

3. ! PROFIT!* * - BY WASTING LESS OF YOUR TIME FAFFING AROUND WITH BASH @DesignatedNerd

Slide 106

Slide 106 text

A CAVEAT @DesignatedNerd

Slide 107

Slide 107 text

DEVELOPERS ARE NOT USED TO SCRIPTING IN SWIFT @DesignatedNerd

Slide 108

Slide 108 text

! " @DesignatedNerd

Slide 109

Slide 109 text

WRITE THE DOCS @DesignatedNerd

Slide 110

Slide 110 text

COMING SOON TO AN APOLLO IOS SDK NEAR YOU! @DesignatedNerd

Slide 111

Slide 111 text

OBLIGATORY SUMMARY SLIDE @DesignatedNerd

Slide 112

Slide 112 text

OBLIGATORY SUMMARY SLIDE > Use swift to write testable script code @DesignatedNerd

Slide 113

Slide 113 text

OBLIGATORY SUMMARY SLIDE > Use swift to write testable script code > Know what broke when stuff breaks @DesignatedNerd

Slide 114

Slide 114 text

OBLIGATORY SUMMARY SLIDE > Use swift to write testable script code > Know what broke when (not if) stuff breaks @DesignatedNerd

Slide 115

Slide 115 text

OBLIGATORY SUMMARY SLIDE > Use swift to write testable script code > Know what broke when (not if) stuff breaks > Dissect your old scripts, rebuild in a Swift Framework @DesignatedNerd

Slide 116

Slide 116 text

OBLIGATORY SUMMARY SLIDE > Use swift to write testable script code > Know what broke when (not if) stuff breaks > Dissect your old scripts, rebuild in a Swift Framework > Test everything you can't release your app without @DesignatedNerd

Slide 117

Slide 117 text

OBLIGATORY SUMMARY SLIDE > Use swift to write testable script code > Know what broke when (not if) stuff breaks > Dissect your old scripts, rebuild in a Swift Framework > Test everything you can't release your app without > Use Swift Package Manager to build an executable @DesignatedNerd

Slide 118

Slide 118 text

OBLIGATORY SUMMARY SLIDE > Use swift to write testable script code > Know what broke when (not if) stuff breaks > Dissect your old scripts, rebuild in a Swift Framework > Test everything you can't release your app without > Use Swift Package Manager to build an executable > Run that from a build script run phase in your main app @DesignatedNerd

Slide 119

Slide 119 text

OBLIGATORY SUMMARY SLIDE > Use swift to write testable script code > Know what broke when (not if) stuff breaks > Dissect your old scripts, rebuild in a Swift Framework > Test everything you can't release your app without > Use Swift Package Manager to build an executable > Run that from a build script run phase in your main app > Document the crap out of everything @DesignatedNerd

Slide 120

Slide 120 text

! THANK YOU! @DesignatedNerd

Slide 121

Slide 121 text

LINKS! ADOPTING SWIFT PACKAGES IN XCODE https://developer.apple.com/videos/play/wwdc2019/408/ CREATING SWIFT PACKAGES https://developer.apple.com/videos/play/wwdc2019/410/ @DesignatedNerd