The Great iOS Concurrency Bake-Off - Swift Usergroup Netherlands, Amsterdam, February 2019

The Great iOS Concurrency Bake-Off - Swift Usergroup Netherlands, Amsterdam, February 2019

An Instant Replay™ of my concurrency talk from mobOS!

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

Original slides (they're pretty damned similar):
https://speakerdeck.com/designatednerd/the-great-ios-concurrency-bake-off-mobos-cluj-napoca-romania-february-2019

C4861b1dfdf3bbb21faec4a1acdf183d?s=128

Ellen Shapiro

February 28, 2019
Tweet

Transcript

  1. The Great iOS Concurrency ! " Bake-Off ! Swift Usergroup

    NL ! | Amsterdam | 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 not 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. None
  80. None
  81. Three competitors

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

  83. None
  84. None
  85. None
  86. The Problem: ! Callback Hell

  87. Three competitors

  88. Three competitors 1. Operator Overloading

  89. Three competitors 1. Operator Overloading 2. PromiseKit

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

  91. ! Signature Challenge:

  92. ! Signature Challenge: Making Code More Readable

  93. Competitor #1: Operator Overloading

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

  95. None
  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. 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)) } } } }
  98. 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)) } } } }
  99. PEMDAS

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

  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. 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)) } } } }
  105. 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)) } } } }
  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. 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)
  108. 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)
  109. ! 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) }
  110. ! 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) }
  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. 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)" } }
  113. 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)" } }
  114. 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)) } } } }
  115. 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)) } } } }
  116. Pro: You Do It Yourself

  117. Con: You Do It Yourself

  118. Competitor #2: PromiseKit

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

    library."
  120. None
  121. None
  122. 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)
  123. 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>
  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. 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)" }
  130. 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)" }
  131. None
  132. Solution #3: RxSwift

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

  134. 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>
  135. 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>
  136. None
  137. Observable

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

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

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

  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. 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)
  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. None
  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. 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)
  147. 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)
  148. None
  149. ! Technical Challenge:

  150. ! Technical Challenge: Handling Threading

  151. 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) }
  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. 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) } }
  154. 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) } }
  155. None
  156. 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)" }
  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 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)" }
  159. 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)" }
  160. PromiseKit 6

  161. PromiseKit 6

  162. PromiseKit 6

  163. PromiseKit 6

  164. PromiseKit 6

  165. PromiseKit 6

  166. None
  167. None
  168. PromiseKit 6

  169. 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)" }
  170. 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)" }
  171. RxSwift:

  172. RxSwift: ! Scheduler

  173. It's not a queue!

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

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

  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) } .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. 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)
  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) } .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. subscribeOn: actually do stuff here

  180. 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)
  181. None
  182. Operator overloading: Hard mode

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

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

    to call back
  185. ! Show-Stopper Challenge:

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

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

    results
  188. Operator Overloading

  189. Operator Overloading

  190. None
  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 { users in debugPrint("Users: \(users)") } .catch { error in debugPrint("Error: \(error)") }
  193. 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)") }
  194. 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)") }
  195. 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)") }
  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. 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)") }
  198. 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)") }
  199. None
  200. None
  201. RxSwift

  202. None
  203. Rx Marble Diagrams

  204. RxSwift

  205. RxSwift

  206. RxSwift

  207. RxSwift

  208. RxSwift

  209. None
  210. None
  211. None
  212. JUST PICK ONE!

  213. ! PromiseKit

  214. None
  215. ! PromiseKit FOR ME

  216. None
  217. ! Killing flies with a sledgehammer

  218. Sometimes you need a sledgehammer

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

  220. Obligatory Summary Slide

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

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

  223. 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.
  224. 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
  225. 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.
  226. None
  227. Choose Wisely

  228. 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
  229. ! 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