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

The Great iOS Concurrency Bake-Off - mobOS, Cluj-Napoca, Romania, February 2019

The Great iOS Concurrency Bake-Off - mobOS, Cluj-Napoca, Romania, February 2019

Abstract:
Callback Hell is a problem affecting developers of all stripes trying to write asynchronous code that's easy to follow. iOS developers don't yet have much in the way of built-in options to reduce callback hell, but there are several libraries which purport to help. In this session, you'll get a look at what problems are being solved by RxSwift and PromiseKit, as well as a look at an intriguing functional technique to avoid callback hell without libraries. And you'll learn more about how to answer the question: Which of these hammers is the most appropriate for my particular nail?

I will also make a ton of references to the Great British Bake Off, which you should watch because it is delightful.

Code:
https://github.com/designatednerd/OperationComparison

C4861b1dfdf3bbb21faec4a1acdf183d?s=128

Ellen Shapiro
PRO

February 14, 2019
Tweet

Transcript

  1. The Great iOS Concurrency ! " Bake-Off mobOS | Cluj-Napoca,

    Romania | February 2019 @DesignatedNerd | bakkenbaeck.no | justhum.com
  2. What is Concurrency?

  3. Concurrency vs. Parallelism

  4. Metaphor Time!

  5. None
  6. None
  7. None
  8. None
  9. None
  10. None
  11. None
  12. None
  13. None
  14. None
  15. None
  16. None
  17. None
  18. None
  19. None
  20. None
  21. None
  22. None
  23. None
  24. None
  25. None
  26. Concurrency: "One thing should do these tasks at as close

    to the same time as possible."
  27. None
  28. None
  29. None
  30. None
  31. None
  32. None
  33. None
  34. None
  35. Parallelism: "Many things should do all these identical mini-tasks at

    the same time."
  36. None
  37. None
  38. None
  39. None
  40. None
  41. None
  42. !

  43. Taking the result of an asynchronous function

  44. Taking the result of an asynchronous function Giving it to

    another asynchronous function
  45. Taking the result of an asynchronous function Giving it to

    another asynchronous function (and another and another)
  46. The Problem: ! Callback Hell

  47. Teh Codez! https://github.com/designatednerd/ OperationComparison

  48. As a user, I want to see a profile picture

    when viewing a profile.
  49. As a developer, I want a list of profiles with

    icons to drop frames when scrolling.
  50. User Data -> Icon Image -> Resized Icon Image static

    func fetchUser(failureCompletion: @escaping (Error) -> Void, successCompletion: @escaping (User) -> Void) static func fetchImage(for user: User, failureCompletion: @escaping (Swift.Error) -> Void, successCompletion: @escaping (UIImage) -> Void) static func resizeImage(_ image: UIImage, to size: CGSize, failureCompletion: @escaping (Swift.Error) -> Void, successCompletion: @escaping (UIImage) -> Void)
  51. Not Horrible... self.activityIndicator.startAnimating() FakeAPI.fetchUser(failureCompletion: { [weak self] error in guard

    let self = self else { return } self.activityIndicator.stopAnimating() self.operationLabel.text = "Error fetching user: \(error)" }, successCompletion: { [weak self] user in guard let self = self else { return } self.operationLabel.text = "Fetching image..." })
  52. Not ideal... self.activityIndicator.startAnimating() FakeAPI.fetchUser(failureCompletion: { [weak self] error in guard

    let self = self else { return } self.activityIndicator.stopAnimating() self.operationLabel.text = "Error fetching user: \(error)" }, successCompletion: { [weak self] user in guard let self = self else { return } self.operationLabel.text = "Fetching image..." RealAPI.fetchImage(for: user, failureCompletion: { [weak self] error in guard let self = self else { return } self.activityIndicator.stopAnimating() self.operationLabel.text = "Error fetching image: \(error)" }, successCompletion: { [weak self] image in guard let self = self else { return } self.operationLabel.text = "Resizing image..." }) })
  53. ...Does anyone have a ? self.activityIndicator.startAnimating() FakeAPI.fetchUser(failureCompletion: { [weak self]

    error in guard let self = self else { return } self.activityIndicator.stopAnimating() self.operationLabel.text = "Error fetching user: \(error)" }, successCompletion: { [weak self] user in guard let self = self else { return } self.operationLabel.text = "Fetching image..." RealAPI.fetchImage(for: user, failureCompletion: { [weak self] error in guard let self = self else { return } self.activityIndicator.stopAnimating() self.operationLabel.text = "Error fetching image: \(error)" }, successCompletion: { [weak self] image in guard let self = self else { return } self.operationLabel.text = "Resizing image..." ImageResizer.resizeImage(image, to: self.imageView.frame.size, failureCompletion: { [weak self] error in guard let self = self else { return } self.activityIndicator.stopAnimating() self.operationLabel.text = "Error resizing image: \(error)" }, successCompletion: { [weak self] resizedImage in guard let self = self else { return } self.activityIndicator.stopAnimating() self.imageView.image = resizedImage self.operationLabel.text = "Complete in \(self.formattedSecondsSince(start))!" }) }) })
  54. ¯\_(ϑ)_/¯ Avoid UI interaction and don't care about retain cycles!

  55. ¯\_(ϑ)_/¯ Avoid UI interaction and don't care about retain cycles!*

    * - not recommended
  56. FakeAPI.fetchUser(failureCompletion: { error in debugPrint("Error fetching user: \(error)") }, successCompletion:

    { user in RealAPI.fetchImage(for: user, failureCompletion: { error in debugPrint("Error fetching image: \(error)") }, successCompletion: { image in ImageResizer.resizeImage(image, to: self.imageView.frame.size, failureCompletion: { error in debugPrint("Error resizing image: \(error)") }, successCompletion: { resizedImage in self.imageView.image = resizedImage debugPrint("Complete in \(self.formattedSecondsSince(start))!" }) }) })
  57. Think Indent Different

  58. FakeAPI.fetchUser( errorCompletion: { error in debugPrint("Error fetching user: \(error)") },

    successCompletion: { user in RealAPI.fetchImage( for: user, failureCompletion: { error in debugPrint("Error fetching image: \(error)") }, successCompletion: { image in ImageResizer.resizeImage( image, to: self.imageView.frame.size, failureCompletion: { error in debugPrint("Error resizing image: \(error)") }, successCompletion: { resizedImage in self.imageView.image = resizedImage debugPrint("Complete in \(self.formattedSecondsSince(start))!" }) }) })
  59. Try to break it a p a r t

  60. private func fetchUser() { FakeAPI.fetchUser( failureCompletion: { error in debugPrint("Error

    fetching user: \(error)") }, successCompletion: { user in self.fetchImage(for: user) }) } private func fetchImage(for user: User) { RealAPI.fetchImage( for: user, failureCompletion: { error in debugPrint("Error fetching image: \(error)") }, successCompletion: { image in self.resizeImage(image, to: self.imageView.frame.size) }) } private func resizeImage(_ image: UIImage, to size: CGSize) { ImageResizer.resizeImage( image, to: self.imageView.frame.size, failureCompletion: { error in debugPrint("Error resizing image: \(error)") }, successCompletion: { [weak self] resizedImage in guard let self = self else { return } self.imageView.image = resizedImage debugPrint("Complete in \(self.formattedSecondsSince(start))!") }) }
  61. The problem: Dependent functions

  62. private func fetchUser() { FakeAPI.fetchUser( failureCompletion: { error in debugPrint("Error

    fetching user: \(error)") }, successCompletion: { user in self.fetchImage(for: user) }) } private func fetchImage(for user: User) { RealAPI.fetchImage( for: user, failureCompletion: { error in debugPrint("Error fetching image: \(error)") }, successCompletion: { image in self.resizeImage(image, to: self.imageView.frame.size) }) } private func resizeImage(_ image: UIImage, to size: CGSize) { ImageResizer.resizeImage( image, to: self.imageView.frame.size, failureCompletion: { error in debugPrint("Error resizing image: \(error)") }, successCompletion: { [weak self] resizedImage in guard let self = self else { return } self.imageView.image = resizedImage debugPrint("Complete in \(self.formattedSecondsSince(start))!") }) }
  63. private func fetchUser() { FakeAPI.fetchUser( failureCompletion: { error in debugPrint("Error

    fetching user: \(error)") }, successCompletion: { user in self.fetchImage(for: user) }) } private func fetchImage(for user: User) { RealAPI.fetchImage( for: user, failureCompletion: { error in debugPrint("Error fetching image: \(error)") }, successCompletion: { image in self.resizeImage(image, to: self.imageView.frame.size) }) } private func resizeImage(_ image: UIImage, to size: CGSize) { ImageResizer.resizeImage( image, to: self.imageView.frame.size, failureCompletion: { error in debugPrint("Error resizing image: \(error)") }, successCompletion: { [weak self] resizedImage in guard let self = self else { return } self.imageView.image = resizedImage debugPrint("Complete in \(self.formattedSecondsSince(start))!") }) }
  64. FakeAPI.fetchUser( errorCompletion: { error in debugPrint("Error fetching user: \(error)") },

    successCompletion: { user in RealAPI.fetchImage( for: user, failureCompletion: { error in debugPrint("Error fetching image: \(error)") }, successCompletion: { image in ImageResizer.resizeImage( image, to: self.imageView.frame.size, failureCompletion: { error in debugPrint("Error resizing image: \(error)") }, successCompletion: { resizedImage in self.imageView.image = resizedImage debugPrint("Complete in \(self.formattedSecondsSince(start))!" }) }) })
  65. A slight help: The Result Type

  66. ! Swift 5.0 https://github.com/apple/swift/ blob/master/stdlib/public/core/ Result.swift

  67. Result.Swift public enum Result<Success, Failure: Error> { /// A success,

    storing a `Success` value. case success(Success) /// A failure, storing a `Failure` value. case failure(Failure) }
  68. User Data -> Icon Image -> Resized Icon Image static

    func fetchUser(failureCompletion: @escaping (Error) -> Void, successCompletion: @escaping (User) -> Void) static func fetchImage(for user: User, failureCompletion: @escaping (Swift.Error) -> Void, successCompletion: @escaping (UIImage) -> Void) static func resizeImage(_ image: UIImage, to size: CGSize, failureCompletion: @escaping (Swift.Error) -> Void, successCompletion: @escaping (UIImage) -> Void)
  69. User Data -> Icon Image -> Resized Icon Image static

    func fetchUser(completion: @escaping (Result<User, Error>) -> Void) static func fetchImage(for user: User, completion: @escaping (Result<UIImage, Error>) -> Void) static func resizeImage(_ image: UIImage, to size: CGSize, failureCompletion: @escaping (Result<UIImage, Error>) -> Void)
  70. FakeAPI.fetchUser { userResult in switch userResult { case .success(let user):

    RealAPI.fetchImage(for: user) { imageResult in switch imageResult { case .success(let image): ImageResizer.resizeImage(image, to: self.imageView.frame.size) { resizedImageResult in switch resizedImageResult { case .success(let resizedImage): self.imageView.image = resizedImage debugPrint("Complete") case .failure(let error): debugPrint("Error resizing image: \(error)") } } case .failure(let error): debugPrint("Error fetching image: \(error)") } } case .failure(let error): debugPrint("Error fetching user: \(error)") } }
  71. FakeAPI.fetchUser { userResult in switch userResult { case .success(let user):

    RealAPI.fetchImage(for: user) { imageResult in switch imageResult { case .success(let image): ImageResizer.resizeImage(image, to: self.imageView.frame.size) { resizedImageResult in switch resizedImageResult { case .success(let resizedImage): self.imageView.image = resizedImage debugPrint("Complete") case .failure(let error): debugPrint("Error resizing image: \(error)") } } case .failure(let error): debugPrint("Error fetching image: \(error)") } } case .failure(let error): debugPrint("Error fetching user: \(error)") } }
  72. FakeAPI.fetchUser { userResult in switch userResult { case .success(let user):

    RealAPI.fetchImage(for: user) { imageResult in switch imageResult { case .success(let image): ImageResizer.resizeImage( image, to: self.imageView.frame.size) { resizedImageResult in switch resizedImageResult { case .success(let resizedImage): self.imageView.image = resizedImage debugPrint("Complete") case .failure(let error): debugPrint("Error resizing image: \(error)") } } case .failure(let error): debugPrint("Error fetching image: \(error)") } } case .failure(let error): debugPrint("Error fetching user: \(error)") } }
  73. FakeAPI.fetchUser { userResult in switch userResult { case .success(let user):

    RealAPI.fetchImage(for: user) { imageResult in switch imageResult { case .success(let image): ImageResizer.resizeImage( image, to: self.imageView.frame.size) { resizedImageResult in switch resizedImageResult { case .success(let resizedImage): self.imageView.image = resizedImage debugPrint("Complete") case .failure(let error): debugPrint("Error resizing image: \(error)") } } case .failure(let error): debugPrint("Error fetching image: \(error)") } } case .failure(let error): debugPrint("Error fetching user: \(error)") } }
  74. How do we chain operations which need each others' results

    clearly?
  75. How do we chain operations which need each others' results

    in an easy-to-follow way?
  76. How do we chain operations which need each others' results

    so we can figure out where something's going wrong?
  77. ! " Bake-Off!

  78. None
  79. Three competitors

  80. Three competitors (out of way more than three)

  81. None
  82. None
  83. None
  84. The Problem: ! Callback Hell

  85. Three competitors

  86. Three competitors 1. Operator Overloading

  87. Three competitors 1. Operator Overloading 2. PromiseKit

  88. Three competitors 1. Operator Overloading 2. PromiseKit 3. RxSwift

  89. ! Signature Challenge:

  90. ! Signature Challenge: Making Code More Readable

  91. Competitor #1: Operator Overloading

  92. Competitor #1: Operator Overloading "We don't need no stinkin' libraries!"

  93. None
  94. typealias ResultCompletion<T> = (Result<T, Error>) -> Void infix operator -->:

    MultiplicationPrecedence func --><T,U>(_ firstFunction: @escaping (@escaping ResultCompletion<T>) -> Void, _ secondFunction: @escaping (T, @escaping ResultCompletion<U>) -> Void) -> (@escaping ResultCompletion<U>) -> Void { return { completion in firstFunction { result in switch result { case .success(let item): secondFunction(item) { result2 in completion(result2) } case .error(let error): completion(.error(error)) } } } }
  95. typealias ResultCompletion<T> = (Result<T, Error>) -> Void infix operator -->:

    MultiplicationPrecedence func --><T,U>(_ firstFunction: @escaping (@escaping ResultCompletion<T>) -> Void, _ secondFunction: @escaping (T, @escaping ResultCompletion<U>) -> Void) -> (@escaping ResultCompletion<U>) -> Void { return { completion in firstFunction { result in switch result { case .success(let item): secondFunction(item) { result2 in completion(result2) } case .error(let error): completion(.error(error)) } } } }
  96. typealias ResultCompletion<T> = (Result<T, Error>) -> Void infix operator -->:

    MultiplicationPrecedence func --><T,U>(_ firstFunction: @escaping (@escaping ResultCompletion<T>) -> Void, _ secondFunction: @escaping (T, @escaping ResultCompletion<U>) -> Void) -> (@escaping ResultCompletion<U>) -> Void { return { completion in firstFunction { result in switch result { case .success(let item): secondFunction(item) { result2 in completion(result2) } case .error(let error): completion(.error(error)) } } } }
  97. PEMDAS

  98. PEMDAS (parentheses, exponents, multiplication, division, addition, subtraction)

  99. typealias ResultCompletion<T> = (Result<T, Error>) -> Void infix operator -->:

    MultiplicationPrecedence func --><T,U>(_ firstFunction: @escaping (@escaping ResultCompletion<T>) -> Void, _ secondFunction: @escaping (T, @escaping ResultCompletion<U>) -> Void) -> (@escaping ResultCompletion<U>) -> Void { return { completion in firstFunction { result in switch result { case .success(let item): secondFunction(item) { result2 in completion(result2) } case .error(let error): completion(.error(error)) } } } }
  100. typealias ResultCompletion<T> = (Result<T, Error>) -> Void infix operator -->:

    MultiplicationPrecedence func --><T,U>(_ firstFunction: @escaping (@escaping ResultCompletion<T>) -> Void, _ secondFunction: @escaping (T, @escaping ResultCompletion<U>) -> Void) -> (@escaping ResultCompletion<U>) -> Void { return { completion in firstFunction { result in switch result { case .success(let item): secondFunction(item) { result2 in completion(result2) } case .error(let error): completion(.error(error)) } } } }
  101. typealias ResultCompletion<T> = (Result<T, Error>) -> Void infix operator -->:

    MultiplicationPrecedence func --><T,U>(_ firstFunction: @escaping (@escaping ResultCompletion<T>) -> Void, _ secondFunction: @escaping (T, @escaping ResultCompletion<U>) -> Void) -> (@escaping ResultCompletion<U>) -> Void { return { completion in firstFunction { result in switch result { case .success(let item): secondFunction(item) { result2 in completion(result2) } case .error(let error): completion(.error(error)) } } } }
  102. typealias ResultCompletion<T> = (Result<T, Error>) -> Void infix operator -->:

    MultiplicationPrecedence func --><T,U>(_ firstFunction: @escaping (@escaping ResultCompletion<T>) -> Void, _ secondFunction: @escaping (T, @escaping ResultCompletion<U>) -> Void) -> (@escaping ResultCompletion<U>) -> Void { return { completion in firstFunction { result in switch result { case .success(let item): secondFunction(item) { result2 in completion(result2) } case .error(let error): completion(.error(error)) } } } }
  103. typealias ResultCompletion<T> = (Result<T, Error>) -> Void infix operator -->:

    MultiplicationPrecedence func --><T,U>(_ firstFunction: @escaping (@escaping ResultCompletion<T>) -> Void, _ secondFunction: @escaping (T, @escaping ResultCompletion<U>) -> Void) -> (@escaping ResultCompletion<U>) -> Void { return { completion in firstFunction { result in switch result { case .success(let item): secondFunction(item) { result2 in completion(result2) } case .error(let error): completion(.error(error)) } } } }
  104. User Data -> Icon Image -> Resized Icon Image static

    func fetchUser(completion: @escaping (Result<User, Error>) -> Void) static func fetchImage(for user: User, completion: @escaping (Result<UIImage, Error>) -> Void) static func resizeImage(_ image: UIImage, to size: CGSize, failureCompletion: @escaping (Result<UIImage, Error>) -> Void)
  105. User Data -> Icon Image -> Resized Icon Image static

    func fetchUser(completion: @escaping (Result<User, Error>) -> Void) static func fetchImage(for user: User, completion: @escaping (Result<UIImage, Error>) -> Void) static func resizeImage(_ image: UIImage, to size: CGSize, failureCompletion: @escaping (Result<UIImage, Error>) -> Void)
  106. User Data -> Icon Image -> Resized Icon Image static

    func fetchUser(completion: @escaping (Result<User, Error>) -> Void) static func fetchImage(for user: User, completion: @escaping (Result<UIImage, Error>) -> Void) static func resizeImage(_ image: UIImage, to size: CGSize, failureCompletion: @escaping (Result<UIImage, Error>) -> Void)
  107. ! Wrapper Methods private func resizeImageForImageView(_ image: UIImage, completion: @escaping

    (Swift.Result<UIImage, Swift.Error>) -> Void) { ImageResizer.resizeImage(image, to: self.imageView.frame.size, completion: completion) }
  108. ! Wrapper Methods private func resizeImageForImageView(_ image: UIImage, completion: @escaping

    (Swift.Result<UIImage, Swift.Error>) -> Void) { self.operationLabel.text = "Resizing image (Custom Operator)..." ImageResizer.resizeImage(image, to: self.imageView.frame.size, completion: completion) } private func fetchUserImage(for user: User, completion: @escaping (Swift.Result<UIImage, Swift.Error>) -> Void) { self.operationLabel.text = "Fetching image (Custom Operator)..." RealAPI.fetchImage(for: user, completion: completion) }
  109. self.activityIndicator.startAnimating() let chain = API.fetchUser --> fetchUserImage --> resizeImageForImageView chain

    { result in self.activityIndicator.stopAnimating() switch result { case .success(let image): self.imageView.image = image self.operationLabel.text = "Complete (Custom Operator)!" case .error(let error): self.operationLabel.text = "Error occurred (Custom Operator): \(error)" } }
  110. self.activityIndicator.startAnimating() let chain = API.fetchUser --> fetchUserImage --> resizeImageForImageView chain

    { result in self.activityIndicator.stopAnimating() switch result { case .success(let image): self.imageView.image = image self.operationLabel.text = "Complete (Custom Operator)!" case .error(let error): self.operationLabel.text = "Error occurred (Custom Operator): \(error)" } }
  111. self.activityIndicator.startAnimating() let chain = API.fetchUser --> fetchUserImage --> resizeImageForImageView chain

    { result in self.activityIndicator.stopAnimating() switch result { case .success(let image): self.imageView.image = image self.operationLabel.text = "Complete (Custom Operator)!" case .error(let error): self.operationLabel.text = "Error occurred (Custom Operator): \(error)" } }
  112. typealias ResultCompletion<T> = (Result<T, Error>) -> Void infix operator -->:

    MultiplicationPrecedence func --><T,U>(_ firstFunction: @escaping (@escaping ResultCompletion<T>) -> Void, _ secondFunction: @escaping (T, @escaping ResultCompletion<U>) -> Void) -> (@escaping ResultCompletion<U>) -> Void { return { completion in firstFunction { result in switch result { case .success(let item): secondFunction(item) { result2 in completion(result2) } case .error(let error): completion(.error(error)) } } } }
  113. typealias ResultCompletion<T> = (Result<T, Error>) -> Void infix operator -->:

    MultiplicationPrecedence func --><T,U>(_ firstFunction: @escaping (@escaping ResultCompletion<T>) -> Void, _ secondFunction: @escaping (T, @escaping ResultCompletion<U>) -> Void) -> (@escaping ResultCompletion<U>) -> Void { return { completion in firstFunction { result in switch result { case .success(let item): secondFunction(item) { result2 in completion(result2) } case .error(let error): completion(.error(error)) } } } }
  114. Pro: You Do It Yourself

  115. Con: You Do It Yourself

  116. Competitor #2: PromiseKit

  117. Competitor #2: PromiseKit "OK, fine. Maybe we do need a

    library."
  118. None
  119. None
  120. User Data -> Icon Image -> Resized Icon Image static

    func fetchUser(completion: @escaping (Result<User, Error>) -> Void) static func fetchImage(for user: User, completion: @escaping (Result<UIImage, Error>) -> Void) static func resizeImage(_ image: UIImage, to size: CGSize, failureCompletion: @escaping (Result<UIImage, Error>) -> Void)
  121. User Data -> Icon Image -> Resized Icon Image static

    func fetchUser() -> Promise<User> static func fetchImage(for user: User) -> Promise<Image> static func resizeImage(_ image: UIImage, to size: CGSize) -> Promise<Image>
  122. self.activityIndicator.startAnimating() FakeAPI.fetchUser() .then { user in RealAPI.fetchImage(for: user) } .then

    { image in ImageResizer.resizeImage(image, to: self.imageView.frame.size) } .ensure { self.activityIndicator.stopAnimating() } .done { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (PromiseKit)!" } .catch { [weak self] error in self?.operationLabel.text = "Error occurred (PromiseKit): \(error)" }
  123. self.activityIndicator.startAnimating() FakeAPI.fetchUser() .then { user in RealAPI.fetchImage(for: user) } .then

    { image in ImageResizer.resizeImage(image, to: self.imageView.frame.size) } .ensure { self.activityIndicator.stopAnimating() } .done { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (PromiseKit)!" } .catch { [weak self] error in self?.operationLabel.text = "Error occurred (PromiseKit): \(error)" }
  124. self.activityIndicator.startAnimating() FakeAPI.fetchUser() .then { user in RealAPI.fetchImage(for: user) } .then

    { image in ImageResizer.resizeImage(image, to: self.imageView.frame.size) } .ensure { self.activityIndicator.stopAnimating() } .done { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (PromiseKit)!" } .catch { [weak self] error in self?.operationLabel.text = "Error occurred (PromiseKit): \(error)" }
  125. self.activityIndicator.startAnimating() FakeAPI.fetchUser() .then { user in RealAPI.fetchImage(for: user) } .then

    { image in ImageResizer.resizeImage(image, to: self.imageView.frame.size) } .ensure { self.activityIndicator.stopAnimating() } .done { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (PromiseKit)!" } .catch { [weak self] error in self?.operationLabel.text = "Error occurred (PromiseKit): \(error)" }
  126. self.activityIndicator.startAnimating() FakeAPI.fetchUser() .then { user in RealAPI.fetchImage(for: user) } .then

    { image in ImageResizer.resizeImage(image, to: self.imageView.frame.size) } .ensure { self.activityIndicator.stopAnimating() } .done { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (PromiseKit)!" } .catch { [weak self] error in self?.operationLabel.text = "Error occurred (PromiseKit): \(error)" }
  127. self.activityIndicator.startAnimating() FakeAPI.fetchUser() .then { user in RealAPI.fetchImage(for: user) } .then

    { image in ImageResizer.resizeImage(image, to: self.imageView.frame.size) } .ensure { self.activityIndicator.stopAnimating() } .done { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (PromiseKit)!" } .catch { [weak self] error in self?.operationLabel.text = "Error occurred (PromiseKit): \(error)" }
  128. self.activityIndicator.startAnimating() FakeAPI.fetchUser() .then { user in RealAPI.fetchImage(for: user) } .then

    { image in ImageResizer.resizeImage(image, to: self.imageView.frame.size) } .ensure { self.activityIndicator.stopAnimating() } .done { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (PromiseKit)!" } .catch { [weak self] error in self?.operationLabel.text = "Error occurred (PromiseKit): \(error)" }
  129. None
  130. Solution #3: RxSwift

  131. Solution #3: RxSwift "Use ALL the libraries!"

  132. User Data -> Icon Image -> Resized Icon Image static

    func fetchUser() -> Promise<User> static func fetchImage(for user: User) -> Promise<Image> static func resizeImage(_ image: UIImage, to size: CGSize) -> Promise<Image>
  133. User Data -> Icon Image -> Resized Icon Image static

    func fetchUser() -> Single<User> static func fetchImage(for user: User) -> Single<Image> static func resizeImage(_ image: UIImage, to size: CGSize) -> Single<Image>
  134. None
  135. Observable

  136. Observable "I could get many values from this, until it

    tells me things are complete or an error has occurred."
  137. Single

  138. Single "This will either succeed or fail once."

  139. FakeAPI.rx.fetchUser() .flatMap { user in RealAPI.rx.fetchImage(for: user) } .flatMap {

    image in ImageResizer.rx.resizeImage(image, to: self.imageView.frame.size) } .subscribe( onSuccess: { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (RxSwift) in \(self.formattedSecondsSince(start))!" }, onError: { error in self.operationLabel.text = "Error occurred (RxSwift): \(error)" } ) .disposed(by: bag)
  140. FakeAPI.rx.fetchUser() .flatMap { user in RealAPI.rx.fetchImage(for: user) } .flatMap {

    image in ImageResizer.rx.resizeImage(image, to: self.imageView.frame.size) } .subscribe( onSuccess: { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (RxSwift) in \(self.formattedSecondsSince(start))!" }, onError: { error in self.operationLabel.text = "Error occurred (RxSwift): \(error)" } ) .disposed(by: bag)
  141. FakeAPI.rx.fetchUser() .flatMap { user in RealAPI.rx.fetchImage(for: user) } .flatMap {

    image in ImageResizer.rx.resizeImage(image, to: self.imageView.frame.size) } .subscribe( onSuccess: { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (RxSwift) in \(self.formattedSecondsSince(start))!" }, onError: { error in self.operationLabel.text = "Error occurred (RxSwift): \(error)" } ) .disposed(by: bag)
  142. None
  143. FakeAPI.rx.fetchUser() .flatMap { user in RealAPI.rx.fetchImage(for: user) } .flatMap {

    image in ImageResizer.rx.resizeImage(image, to: self.imageView.frame.size) } .subscribe( onSuccess: { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (RxSwift) in \(self.formattedSecondsSince(start))!" }, onError: { error in self.operationLabel.text = "Error occurred (RxSwift): \(error)" } ) .disposed(by: bag)
  144. FakeAPI.rx.fetchUser() .flatMap { user in RealAPI.rx.fetchImage(for: user) } .flatMap {

    image in ImageResizer.rx.resizeImage(image, to: self.imageView.frame.size) } .subscribe( onSuccess: { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (RxSwift) in \(self.formattedSecondsSince(start))!" }, onError: { error in self.operationLabel.text = "Error occurred (RxSwift): \(error)" } ) .disposed(by: bag)
  145. FakeAPI.rx.fetchUser() .flatMap { user in RealAPI.rx.fetchImage(for: user) } .flatMap {

    image in ImageResizer.rx.resizeImage(image, to: self.imageView.frame.size) } .subscribe( onSuccess: { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (RxSwift) in \(self.formattedSecondsSince(start))!" }, onError: { error in self.operationLabel.text = "Error occurred (RxSwift): \(error)" } ) .disposed(by: bag)
  146. None
  147. ! Technical Challenge:

  148. ! Technical Challenge: Handling Threading

  149. Operator Overloading private func fetchUserImage(for user: User, completion: @escaping (Result<UIImage>)

    -> Void) { self.operationLabel.text = "Fetching image (Custom Operator)..." API.fetchImage(for: user, completion: completion) }
  150. Operator Overloading private func fetchUserImage(for user: User, completion: @escaping (Result<UIImage>)

    -> Void) { self.operationLabel.text = "Fetching image (Custom Operator)..." DispatchQueue.global(qos: .background).async { API.fetchImage(for: user, completion: completion) } }
  151. Operator Overloading private func fetchUserImage(for user: User, completion: @escaping (Result<UIImage>)

    -> Void) { self.operationLabel.text = "Fetching image (Custom Operator)..." DispatchQueue.global(qos: .background).async { API.fetchImage(for: user, completion: completion) } }
  152. Operator Overloading private func fetchUserImage(for user: User, completion: @escaping (Result<UIImage>)

    -> Void) { self.operationLabel.text = "Fetching image (Custom Operator)..." DispatchQueue.global(qos: .background).async { API.fetchImage(for: user, completion: completion) } }
  153. None
  154. PromiseKit FakeAPI.fetchUser() .then { user in RealAPI.fetchImage(for: user) } .then

    { image in ImageResizer.resizeImage(image, to: self.imageView.frame.size) } .ensure { self.activityIndicator.stopAnimating() } .done { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (PromiseKit)!" } .catch { [weak self] error in self?.operationLabel.text = "Error occurred (PromiseKit): \(error)" }
  155. PromiseKit FakeAPI.fetchUser() .then(on: .global(qos: .background)) { user in RealAPI.fetchImage(for: user)

    } .then(on: .global(qos: .background)) { image in ImageResizer.resizeImage(image, to: self.imageView.frame.size) } .ensure { self.activityIndicator.stopAnimating() } .done { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (PromiseKit)!" } .catch { [weak self] error in self?.operationLabel.text = "Error occurred (PromiseKit): \(error)" }
  156. PromiseKit FakeAPI.fetchUser() .then(on: .global(qos: .background)) { user in RealAPI.fetchImage(for: user)

    } .then(on: .global(qos: .background)) { image in ImageResizer.resizeImage(image, to: self.imageView.frame.size) } .ensure { self.activityIndicator.stopAnimating() } .done { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (PromiseKit)!" } .catch { [weak self] error in self?.operationLabel.text = "Error occurred (PromiseKit): \(error)" }
  157. PromiseKit FakeAPI.fetchUser() .then(on: .global(qos: .background)) { user in RealAPI.fetchImage(for: user)

    } .then(on: .global(qos: .background)) { image in ImageResizer.resizeImage(image, to: self.imageView.frame.size) } .ensure { self.activityIndicator.stopAnimating() } .done { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (PromiseKit)!" } .catch { [weak self] error in self?.operationLabel.text = "Error occurred (PromiseKit): \(error)" }
  158. PromiseKit 6

  159. PromiseKit 6

  160. PromiseKit 6

  161. PromiseKit 6

  162. PromiseKit 6

  163. PromiseKit 6

  164. None
  165. None
  166. PromiseKit 6

  167. PromiseKit FakeAPI.fetchUser() .then(on: .global(qos: .background)) { user in RealAPI.fetchImage(for: user)

    } .map { image in (image, self.imageView.frame.size) } .then(on: .global(qos: .background)) { tuple in ImageResizer.resizeImage(tuple.0, to: tuple.1) } .then(on: .global(qos: .background)) { image in ImageResizer.resizeImage(image, to: self.imageView.frame.size) } .ensure { self.activityIndicator.stopAnimating() } .done { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (PromiseKit)!" } .catch { [weak self] error in self?.operationLabel.text = "Error occurred (PromiseKit): \(error)" }
  168. PromiseKit FakeAPI.fetchUser() .then(on: .global(qos: .background)) { user in RealAPI.fetchImage(for: user)

    } .map { image in (image, self.imageView.frame.size) } .then(on: .global(qos: .background)) { tuple in ImageResizer.resizeImage(tuple.0, to: tuple.1) } .then(on: .global(qos: .background)) { image in ImageResizer.resizeImage(image, to: self.imageView.frame.size) } .ensure { self.activityIndicator.stopAnimating() } .done { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (PromiseKit)!" } .catch { [weak self] error in self?.operationLabel.text = "Error occurred (PromiseKit): \(error)" }
  169. RxSwift:

  170. RxSwift: ! Scheduler

  171. It's not a queue!

  172. It's not a queue! (it's just way easier to think

    of it as one)
  173. observeOn: tell me what happened here

  174. RxSwift FakeAPI.rx.fetchUser() .flatMap { user in RealAPI.rx.fetchImage(for: user) } .flatMap

    { image in ImageResizer.rx.resizeImage(image, to: self.imageView.frame.size) } .subscribe( onSuccess: { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (RxSwift) in \(self.formattedSecondsSince(start))!" }, onError: { error in self.operationLabel.text = "Error occurred (RxSwift): \(error)" } ) .disposed(by: bag)
  175. RxSwift FakeAPI.rx.fetchUser() .flatMap { user in RealAPI.rx.fetchImage(for: user) } .flatMap

    { image in ImageResizer.rx.resizeImage(image, to: self.imageView.frame.size) } .observeOn(MainScheduler.instance) .subscribe( onSuccess: { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (RxSwift) in \(self.formattedSecondsSince(start))!" }, onError: { error in self.operationLabel.text = "Error occurred (RxSwift): \(error)" } ) .disposed(by: bag)
  176. RxSwift FakeAPI.rx.fetchUser() .flatMap { user in RealAPI.rx.fetchImage(for: user) } .flatMap

    { image in ImageResizer.rx.resizeImage(image, to: self.imageView.frame.size) } .observeOn(MainScheduler.instance) .subscribe( onSuccess: { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (RxSwift) in \(self.formattedSecondsSince(start))!" }, onError: { error in self.operationLabel.text = "Error occurred (RxSwift): \(error)" } ) .disposed(by: bag)
  177. subscribeOn: actually do stuff here

  178. RxSwift FakeAPI.rx.fetchUser() .flatMap { user in RealAPI.rx.fetchImage(for: user) } .flatMap

    { image in ImageResizer.rx.resizeImage(image, to: self.imageView.frame.size) } .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) .observeOn(MainScheduler.instance) .subscribe( onSuccess: { resizedImage in self.imageView.image = resizedImage self.operationLabel.text = "Complete (RxSwift) in \(self.formattedSecondsSince(start))!" }, onError: { error in self.operationLabel.text = "Error occurred (RxSwift): \(error)" } ) .disposed(by: bag)
  179. None
  180. Operator overloading: Hard mode

  181. PromiseKit: Perform on a queue you specify Call back on

    a pre-defined queue
  182. RxSwift: Always specify where to do stuff Always specify where

    to call back
  183. ! Show-Stopper Challenge:

  184. ! Show-Stopper Challenge: Multi-Function Concurrency

  185. Two functions at the same time Do something with both

    results
  186. Operator Overloading

  187. Operator Overloading

  188. None
  189. PromiseKit: Same Type let userPromise1 = FakeAPI.fetchUser() let userPromise2 =

    FakeAPI.fetchUser() when(fulfilled: [userPromise1, userPromise2]) .done { users in debugPrint("Users: \(users)") } .catch { error in debugPrint("Error: \(error)") }
  190. PromiseKit: Same Type let userPromise1 = FakeAPI.fetchUser() let userPromise2 =

    FakeAPI.fetchUser() when(fulfilled: [userPromise1, userPromise2]) .done { users in debugPrint("Users: \(users)") } .catch { error in debugPrint("Error: \(error)") }
  191. PromiseKit: Same Type let userPromise1 = FakeAPI.fetchUser() let userPromise2 =

    FakeAPI.fetchUser() when(fulfilled: [userPromise1, userPromise2]) .done { users in debugPrint("Users: \(users)") } .catch { error in debugPrint("Error: \(error)") }
  192. PromiseKit: Same Type let userPromise1 = FakeAPI.fetchUser() let userPromise2 =

    FakeAPI.fetchUser() when(fulfilled: userPromise1, userPromise2) .done { user1, user2 in debugPrint("User1: \(user1), User2: \(user2)") } .catch { error in debugPrint("Error: \(error)") }
  193. PromiseKit: Same Type let userPromise1 = FakeAPI.fetchUser() let userPromise2 =

    FakeAPI.fetchUser() when(fulfilled: userPromise1, userPromise2) .done { user1, user2 in debugPrint("User1: \(user1), User2: \(user2)") } .catch { error in debugPrint("Error: \(error)") }
  194. PromiseKit: Different Types let userPromise1 = FakeAPI.fetchUser() let imagePromise =

    ImageResizer.resizeImage(UIImage(), to: self.imageView.frame.size) when(fulfilled: userPromise1, imagePromise) .done { user, image in debugPrint("User: \(user), image: \(image)") } .catch { error in debugPrint("Error: \(error)") }
  195. PromiseKit: Different Types let userPromise1 = FakeAPI.fetchUser() let imagePromise =

    ImageResizer.resizeImage(UIImage(), to: self.imageView.frame.size) when(fulfilled: userPromise1, imagePromise) .done { user, image in debugPrint("User: \(user), image: \(image)") } .catch { error in debugPrint("Error: \(error)") }
  196. PromiseKit: Different Types let userPromise1 = FakeAPI.fetchUser() let imagePromise =

    ImageResizer.resizeImage(UIImage(), to: self.imageView.frame.size) when(fulfilled: userPromise1, imagePromise) .done { user, image in debugPrint("User: \(user), image: \(image)") } .catch { error in debugPrint("Error: \(error)") }
  197. None
  198. None
  199. RxSwift

  200. None
  201. Rx Marble Diagrams

  202. RxSwift

  203. RxSwift

  204. RxSwift

  205. RxSwift

  206. RxSwift

  207. None
  208. None
  209. None
  210. JUST PICK ONE!

  211. ! PromiseKit

  212. ! PromiseKit FOR ME

  213. None
  214. ! Killing flies with a sledgehammer

  215. Sometimes you need a sledgehammer

  216. Most of the time you only need a fly swatter

  217. Obligatory Summary Slide

  218. Obligatory Summary Slide → Harder concurrency is it looks than.

  219. Obligatory Summary Slide → Concurrency is harder than it looks.

  220. Obligatory Summary Slide → Concurrency is harder than it looks.

    → You can try to handle it yourself, but it's going to be a difficult time sink.
  221. Obligatory Summary Slide → Concurrency is harder than it looks.

    → You can try to handle it yourself, but it's going to be a difficult time sink. → PromiseKit is great for most concurrency handling and readability
  222. Obligatory Summary Slide → Concurrency is harder than it looks.

    → You can try to handle it yourself, but it's going to be a difficult time sink. → PromiseKit is great for most concurrency handling and readability → RxSwift is a giant hammer...but sometimes you need a giant hammer.
  223. None
  224. Choose Wisely

  225. Links! → Vincent Pradeilles' Function Composition Talk https://vimeo.com/292702159 → John

    Sundell's Tasks post https://www.swiftbysundell.com/posts/ task-based-concurrency-in-swift → RxMarbles Visualization https://rxmarbles.com
  226. ! The Future! → Chris Lattner's Concurrency Manifesto https://gist.github.com/lattner/ 31ed37682ef1576b16bca1432ea9f782

    → Kotlin Coroutines: Async/Await + More https://kotlinlang.org/docs/reference/ coroutines-overview.html