$30 off During Our Annual Pro Sale. View Details »

Digging In To The Apollo iOS SDK - GraphQL Summit, San Francisco, October 2019

Digging In To The Apollo iOS SDK - GraphQL Summit, San Francisco, October 2019

Learn about the what and why of recent updates to ApolloGraphQL’s Swift iOS SDK, including updates to code generation, get a few concepts on the iOS side more clearly explained, and learn about where we're going with the iOS SDK!

(Will link to video as soon as it's up!)

Ellen Shapiro
PRO

October 31, 2019
Tweet

More Decks by Ellen Shapiro

Other Decks in Technology

Transcript


  1. Digging in
    to the Apollo iOS SDK
    GraphQL Summit | San Francisco, CA | October 2019
    by Ellen Shapiro | @DesignatedNerd | apollographql.com

    View Slide

  2. !

    View Slide

  3. !

    View Slide

  4. !

    View Slide

  5. !

    View Slide

  6. !

    View Slide

  7. !

    View Slide

  8. !

    View Slide

  9. ! "

    View Slide

  10. !

    View Slide

  11. !

    View Slide

  12. View Slide

  13. ! ☠

    View Slide


  14. View Slide

  15. View Slide

  16. View Slide

  17. May 1, 2019

    View Slide

  18. View Slide

  19. !

    View Slide

  20. View Slide

  21. !

    View Slide

  22. View Slide

  23. View Slide

  24. October 30, 2019

    View Slide

  25. What Changed?

    View Slide

  26. Getting Started

    View Slide

  27. Adding an iOS library, normally
    1. Install dependency using one of:
    → pod install
    → carthage checkout
    → swift build

    View Slide

  28. Adding an iOS library, normally
    1. Install dependency using one of:
    → pod install
    → carthage checkout
    → swift build
    2. Use library.

    View Slide

  29. Code Generation

    View Slide

  30. schema + queries = code

    View Slide

  31. schema + queries + = code

    View Slide

  32. !
    Type Safety
    through the whole stack

    View Slide

  33. Codegen needs to know

    View Slide

  34. Codegen needs to know
    → Where is the schema file?

    View Slide

  35. Codegen needs to know
    → Where is the schema file?
    → Where are the query files?

    View Slide

  36. Codegen needs to know
    → Where is the schema file?
    → Where are the query files?
    → Where should generated code be output?

    View Slide

  37. Codegen needs to know
    → Where is the schema file?
    → Where are the query files?
    → Where should generated code be output?
    → Any other options which are available from the
    codegen library

    View Slide

  38. Node + TypeScript

    View Slide

  39. ! " !

    View Slide

  40. Adding apollo-ios before

    View Slide

  41. Adding apollo-ios before
    1. Install dependency

    View Slide

  42. Adding apollo-ios before
    1. Install dependency
    2. Add build script

    View Slide

  43. Adding apollo-ios before
    1. Install dependency
    2. Add build script
    3. npm install apollo

    View Slide

  44. Adding apollo-ios before
    1. Install dependency
    2. Add build script
    3. npm install apollo
    4. brew install npm

    View Slide

  45. Adding apollo-ios before
    1. Install dependency
    2. Add build script
    3. npm install apollo
    4. brew install npm
    5. Google how to install Homebrew

    View Slide

  46. Adding apollo-ios before
    1. Install dependency
    2. Add build script
    3. npm install apollo
    4. brew install npm
    5. Google how to install Homebrew
    6.

    View Slide

  47. Adding apollo-ios before
    1. Install dependency
    2. Add build script
    3. npm install apollo

    View Slide

  48. Adding apollo-ios before
    1. Install dependency
    2. Add build script
    3. npm install apollo
    4. Wait, what version of the CLI is this?

    View Slide

  49. Adding apollo-ios before
    1. Install dependency
    2. Add build script
    3. npm install apollo
    4. Wait, what version of the CLI is this?
    5. How do I tell what's installed globally vs locally?

    View Slide

  50. Adding apollo-ios before
    1. Install dependency
    2. Add build script
    3. npm install apollo
    4. Wait, what version of the CLI is this?
    5. How do I tell what's installed globally vs locally?
    6.

    View Slide

  51. !

    View Slide

  52. !

    View Slide

  53. !

    npm

    View Slide

  54. View Slide

  55. View Slide

  56. View Slide

  57. Adding apollo-ios now

    View Slide

  58. Adding apollo-ios now
    1. Install dependency

    View Slide

  59. Adding apollo-ios now
    1. Install dependency
    2. Add build script that calls run-bundled-codegen.sh

    View Slide

  60. Adding apollo-ios now
    1. Install dependency
    2. Add build script that calls run-bundled-codegen.sh
    3. Apollo downloads and unzips the correct version of
    the CLI, all dependencies, and a node runtime.

    View Slide

  61. Adding apollo-ios now
    1. Install dependency
    2. Add build script that calls run-bundled-codegen.sh
    3. Apollo downloads and unzips the correct version of
    the CLI, all dependencies, and a node runtime.
    4. Use library.

    View Slide

  62. !

    View Slide

  63. Difficult Concepts
    need better explanations

    View Slide

  64. Double Optionals??

    View Slide

  65. public struct ReviewInput: GraphQLMapConvertible {
    public init(stars: Int,
    commentary: Optional = nil,
    favoriteColor: Optional = nil) {
    // code
    }
    }

    View Slide

  66. Optional

    View Slide

  67. String??

    View Slide

  68. Optional>

    View Slide

  69. Optional>
    Outer optional:
    "Is a value even being included for this?"

    View Slide

  70. Optional>
    Inner optional:
    "A value was included. Is it nil or String?"

    View Slide

  71. View Slide

  72. View Slide

  73. View Slide

  74. View Slide

  75. public struct ReviewInput: GraphQLMapConvertible {
    public init(stars: Int,
    commentary: Optional = nil,
    favoriteColor: Optional = nil) {
    // code
    }
    }

    View Slide

  76. View Slide

  77. GraphQL fields are
    nullable by default

    View Slide

  78. A nullable field can be absent

    View Slide

  79. public struct ReviewInput: GraphQLMapConvertible {
    public init(stars: Int,
    commentary: Optional = nil,
    favoriteColor: Optional = nil) {
    // code
    }
    }

    View Slide

  80. ReviewInput(stars: 5)
    // { "stars": 5 }
    ReviewInput(stars: 2, commentary: "Needs more ewoks")
    // { "stars": 2, "commentary": "Needs more ewoks" }
    ReviewInput(stars: 3, commentary: nil)
    // { "stars": 3 }
    ReviewInput(stars: 4, commentary: .some(nil))
    // { "stars": 4, "commentary": null }

    View Slide

  81. ReviewInput(stars: 5)
    // { "stars": 5 }
    ReviewInput(stars: 2, commentary: "Needs more ewoks")
    // { "stars": 2, "commentary": "Needs more ewoks" }
    ReviewInput(stars: 3, commentary: nil)
    // { "stars": 3 }
    ReviewInput(stars: 4, commentary: .some(nil))
    // { "stars": 4, "commentary": null }

    View Slide

  82. ReviewInput(stars: 5)
    // { "stars": 5 }
    ReviewInput(stars: 2, commentary: "Needs more ewoks")
    // { "stars": 2, "commentary": "Needs more ewoks" }
    ReviewInput(stars: 3, commentary: nil)
    // { "stars": 3 }
    ReviewInput(stars: 4, commentary: .some(nil))
    // { "stars": 4, "commentary": null }

    View Slide

  83. ReviewInput(stars: 5)
    // { "stars": 5 }
    ReviewInput(stars: 2, commentary: "Needs more ewoks")
    // { "stars": 2, "commentary": "Needs more ewoks" }
    ReviewInput(stars: 3, commentary: nil)
    // { "stars": 3 }
    ReviewInput(stars: 4, commentary: .some(nil))
    // { "stars": 4, "commentary": null }

    View Slide

  84. ReviewInput(stars: 5)
    // { "stars": 5 }
    ReviewInput(stars: 2, commentary: "Needs more ewoks")
    // { "stars": 2, "commentary": "Needs more ewoks" }
    ReviewInput(stars: 3, commentary: nil)
    // { "stars": 3 }
    ReviewInput(stars: 4, commentary: .some(nil))
    // { "stars": 4, "commentary": null }

    View Slide

  85. ReviewInput(stars: 5)
    // { "stars": 5 }
    ReviewInput(stars: 2, commentary: "Needs more ewoks")
    // { "stars": 2, "commentary": "Needs more ewoks" }
    ReviewInput(stars: 3, commentary: nil)
    // { "stars": 3 }
    ReviewInput(stars: 4, commentary: .some(nil))
    // { "stars": 4, "commentary": null }

    View Slide

  86. Caching

    View Slide

  87. Caching
    NormalizedCache

    View Slide

  88. With what
    shall we normalize it?

    View Slide

  89. By default: The
    path of the query

    View Slide

  90. View Slide

  91. View Slide

  92. View Slide

  93. View Slide

  94. View Slide

  95. View Slide

  96. View Slide

  97. GraphQLID

    View Slide

  98. ISBN

    View Slide

  99. ISBN + UUID

    View Slide

  100. ¯\_(ϑ)_/¯

    View Slide

  101. cacheKeyForObject

    View Slide

  102. cacheKeyForObject
    (_ object: JSONObject) -> JSONValue?

    View Slide

  103. apollo.cacheKeyForObject = { $0["id"] }

    View Slide

  104. apollo.cacheKeyForObject = {
    guard
    let id = $0["id"],
    let typename = $0["__typename"] else {
    // Not enough info to uniquely identify the object
    return nil
    }
    return [id, typename]
    }

    View Slide

  105. apollo.cacheKeyForObject = {
    guard
    let id = $0["id"],
    let typename = $0["__typename"] else {
    // Not enough info to uniquely identify the object
    return nil
    }
    return [id, typename]
    }

    View Slide

  106. apollo.cacheKeyForObject = {
    guard
    let id = $0["id"] as? String,
    let typename = $0["__typename"] as? String else {
    // Not enough info to uniquely identify the object
    return nil
    }
    return id + typename
    }

    View Slide

  107. apollo.cacheKeyForObject = {
    guard
    let isbn = $0["isbn"],
    let uuid = $0["uuid"] else {
    // Not enough info to uniquely identify the object
    return nil
    }
    return [isbn, uuid]
    }

    View Slide

  108. In-memory Caching

    View Slide

  109. In-memory Caching
    InMemoryNormalizedCache

    View Slide

  110. Supplementary libraries

    View Slide

  111. SQLite Caching

    View Slide

  112. SQLite Caching
    ApolloSQLite

    View Slide

  113. Subscriptions

    View Slide

  114. Episode Stars Commentary
    Empire ̣̣̣̣̣ This movie rocks!
    New Hope ̣̣̣̣̤ This is the first
    movie, why is it
    episode 4?

    View Slide

  115. Episode Stars Commentary
    Empire ̣̣̣̣̣ This movie rocks!
    New Hope ̣̣̣̣̤ This is the first
    movie, why is it
    episode 4?
    Jedi ̣̣̤̤̤ Needs more
    ewoks.

    View Slide

  116. Subscriptions
    ApolloWebSocket

    View Slide

  117. Subscriptions
    ApolloWebSocket
    (based on Starscream)

    View Slide

  118. client.subscribe(subscription: ReviewAddedSubscription()) { result in
    switch result {
    case .success(let graphQLResult):
    if let review = graphQLResult.data?.reviewAdded {
    // Handle review being added.
    }
    if let errors = graphQLResult.errors {
    // Handle error from the server
    }
    case .failure(let error):
    // Handle networking error
    }
    }

    View Slide

  119. client.subscribe(subscription: ReviewAddedSubscription()) { result in
    switch result {
    case .success(let graphQLResult):
    if let review = graphQLResult.data?.reviewAdded {
    // Handle review being added.
    }
    if let errors = graphQLResult.errors {
    // Handle error from the server
    }
    case .failure(let error):
    // Handle networking error
    }
    }

    View Slide

  120. client.subscribe(subscription: ReviewAddedSubscription()) { result in
    switch result {
    case .success(let graphQLResult):
    if let review = graphQLResult.data?.reviewAdded {
    // Handle review being added.
    }
    if let errors = graphQLResult.errors {
    // Handle error from the server
    }
    case .failure(let error):
    // Handle networking error
    }
    }

    View Slide

  121. client.subscribe(subscription: ReviewAddedSubscription()) { result in
    switch result {
    case .success(let graphQLResult):
    if let review = graphQLResult.data?.reviewAdded {
    // Handle review being added.
    }
    if let errors = graphQLResult.errors {
    // Handle error from the server
    }
    case .failure(let error):
    // Handle networking error
    }
    }

    View Slide

  122. var subscription: Cancellable?
    self.subscription = client
    .subscribe(subscription: ReviewAddedSubscription()) { [weak self] result in
    guard let self = self else {
    return
    }
    switch result {
    case .success(let graphQLResult):
    if let review = graphQLResult.data?.reviewAdded {
    // Handle review being added.
    }
    if let errors = graphQLResult.errors {
    // Handle error from the server
    }
    case .failure(let error):
    // Handle networking error
    }
    }
    dealloc {
    self.subscription?.cancel()
    }

    View Slide

  123. var subscription: Cancellable?
    self.subscription = client
    .subscribe(subscription: ReviewAddedSubscription()) { [weak self] result in
    guard let self = self else {
    return
    }
    switch result {
    case .success(let graphQLResult):
    if let review = graphQLResult.data?.reviewAdded {
    // Handle review being added.
    }
    if let errors = graphQLResult.errors {
    // Handle error from the server
    }
    case .failure(let error):
    // Handle networking error
    }
    }
    dealloc {
    self.subscription?.cancel()
    }

    View Slide

  124. var subscription: Cancellable?
    self.subscription = client
    .subscribe(subscription: ReviewAddedSubscription()) { [weak self] result in
    guard let self = self else {
    return
    }
    switch result {
    case .success(let graphQLResult):
    if let review = graphQLResult.data?.reviewAdded {
    // Handle review being added.
    }
    if let errors = graphQLResult.errors {
    // Handle error from the server
    }
    case .failure(let error):
    // Handle networking error
    }
    }
    dealloc {
    self.subscription?.cancel()
    }

    View Slide

  125. var subscription: Cancellable?
    self.subscription = client
    .subscribe(subscription: ReviewAddedSubscription()) { [weak self] result in
    guard let self = self else {
    return
    }
    switch result {
    case .success(let graphQLResult):
    if let review = graphQLResult.data?.reviewAdded {
    // Handle review being added.
    }
    if let errors = graphQLResult.errors {
    // Handle error from the server
    }
    case .failure(let error):
    // Handle networking error
    }
    }
    dealloc {
    self.subscription?.cancel()
    }

    View Slide

  126. var subscription: Cancellable?
    self.subscription = client
    .subscribe(subscription: ReviewAddedSubscription()) { [weak self] result in
    guard let self = self else {
    return
    }
    switch result {
    case .success(let graphQLResult):
    if let review = graphQLResult.data?.reviewAdded {
    // Handle review being added.
    }
    if let errors = graphQLResult.errors {
    // Handle error from the server
    }
    case .failure(let error):
    // Handle networking error
    }
    }
    dealloc {
    self.subscription?.cancel()
    }

    View Slide

  127. View Slide

  128. !
    The Future

    View Slide

  129. View Slide

  130. View Slide

  131. !
    Free-form responses

    View Slide

  132. → "Poor tutorial support"

    View Slide

  133. → "Poor tutorial support"
    → "Better documentation."

    View Slide

  134. → "Poor tutorial support"
    → "Better documentation."
    → "More documentation on uploading techniques,
    caching, and background communication."

    View Slide

  135. → "Poor tutorial support"
    → "Better documentation."
    → "More documentation on uploading techniques,
    caching, and background communication."
    → "Expand documentation on SQLiteNormalizedCache"

    View Slide

  136. Better Documentation

    View Slide

  137. More Comprehensive Tutorial

    View Slide

  138. I have IDEAS!

    View Slide

  139. I have IDEAS!
    ...Are these good ideas?

    View Slide

  140. I have IDEAS!
    ...Are these good ideas?

    View Slide

  141. Use Codable instead of custom parsing
    to the extent that this is possible

    View Slide

  142. Use Codable instead of custom parsing

    View Slide

  143. Contributing to codegen when it's written in
    Typescript is hard

    View Slide

  144. Swift Codegen

    View Slide

  145. Swift Codegen
    (with parsing and validation still via TypeScript, probably)

    View Slide

  146. Generating Swift Code
    in Swift

    View Slide

  147. As part of Swift Codegen:

    View Slide

  148. As part of Swift Codegen:
    → Codable conformance

    View Slide

  149. As part of Swift Codegen:
    → Codable conformance
    → Equatable/Hashable conformance

    View Slide

  150. As part of Swift Codegen:
    → Codable conformance
    → Equatable/Hashable conformance
    → xcfilelists of inputs and outputs

    View Slide

  151. As part of Swift Codegen:
    → Codable conformance
    → Equatable/Hashable conformance
    → xcfilelists of inputs and outputs
    → Fragments as protocols

    View Slide

  152. As part of Swift Codegen:
    → Codable conformance
    → Equatable/Hashable conformance
    → xcfilelists of inputs and outputs
    → Fragments as protocols
    → Get rid of double-optionals for clearer type*

    View Slide

  153. As part of Swift Codegen:
    → Codable conformance
    → Equatable/Hashable conformance
    → xcfilelists of inputs and outputs
    → Fragments as protocols
    → Get rid of double-optionals for clearer type*
    → Identifiable auto-conformance*

    View Slide

  154. As part of Swift Codegen:
    → Codable conformance
    → Equatable/Hashable conformance
    → xcfilelists of inputs and outputs
    → Fragments as protocols
    → Get rid of double-optionals for clearer type*
    → Identifiable auto-conformance*
    * - I hope

    View Slide

  155. View Slide

  156. "Version x.y.z breaks on ________"

    View Slide

  157. Dependency Manager Test Suite

    View Slide

  158. "Version x.y.z breaks on NOTHING"

    View Slide

  159. Better Caching

    View Slide

  160. Moar
    !
    Libraries!

    View Slide

  161. Conucrrency is hard

    View Slide

  162. Concurrency is hard

    View Slide

  163. Wrappers for RxSwift, Combine, and PromiseKit

    View Slide

  164. LIST SUBJECT TO CHANGE
    Combine, PromiseKit,

    View Slide

  165. !

    View Slide

  166. Stuff you contribute!

    View Slide

  167. Stuff you contribute!
    (seriously though, I would love your help)

    View Slide

  168. View Slide

  169. Obligatory Summary Slide

    View Slide

  170. Obligatory Summary Slide
    → Check out updated build setup, it's way easier

    View Slide

  171. Obligatory Summary Slide
    → Check out updated build setup, it's way easier
    → Docs have been significantly improved

    View Slide

  172. Obligatory Summary Slide
    → Check out updated build setup, it's way easier
    → Docs have been significantly improved
    → New sample app and tutorial coming soon!

    View Slide

  173. Obligatory Summary Slide
    → Check out updated build setup, it's way easier
    → Docs have been significantly improved
    → New sample app and tutorial coming soon!
    → Swift Codegen is coming

    View Slide

  174. Obligatory Summary Slide
    → Check out updated build setup, it's way easier
    → Docs have been significantly improved
    → New sample app and tutorial coming soon!
    → Swift Codegen is coming
    → Check out the ROADMAP.md

    View Slide

  175. Obligatory Summary Slide
    → Check out updated build setup, it's way easier
    → Docs have been significantly improved
    → New sample app and tutorial coming soon!
    → Swift Codegen is coming
    → Check out the ROADMAP.md
    → Contribute!

    View Slide

  176. !
    Thank you!

    View Slide

  177. Links!
    → Main Repo:
    https://github.com/apollographql/apollo-ios
    → Prettily rendered documentation:
    https://apollographql.com/docs/ios/

    View Slide