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. 1.

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

    NL ! | Amsterdam | February 2019 @DesignatedNerd | bakkenbaeck.no | justhum.com
  2. 5.
  3. 6.
  4. 7.
  5. 8.
  6. 9.
  7. 10.
  8. 11.
  9. 12.
  10. 13.
  11. 14.
  12. 15.
  13. 16.
  14. 17.
  15. 18.
  16. 19.
  17. 20.
  18. 21.
  19. 22.
  20. 23.
  21. 24.
  22. 25.
  23. 27.
  24. 28.
  25. 29.
  26. 30.
  27. 31.
  28. 32.
  29. 33.
  30. 34.
  31. 36.
  32. 37.
  33. 38.
  34. 39.
  35. 40.
  36. 41.
  37. 42.

    !

  38. 45.

    Taking the result of an asynchronous function Giving it to

    another asynchronous function (and another and another)
  39. 48.
  40. 49.

    As a developer, I want a list of profiles with

    icons to not drop frames when scrolling.
  41. 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)
  42. 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..." })
  43. 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..." }) })
  44. 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))!" }) }) })
  45. 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))!" }) }) })
  46. 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))!" }) }) })
  47. 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))!") }) }
  48. 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))!") }) }
  49. 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))!") }) }
  50. 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))!" }) }) })
  51. 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) }
  52. 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)
  53. 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)
  54. 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)") } }
  55. 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)") } }
  56. 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)") } }
  57. 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)") } }
  58. 76.

    How do we chain operations which need each others' results

    so we can figure out where something's going wrong?
  59. 78.
  60. 79.
  61. 80.
  62. 83.
  63. 84.
  64. 85.
  65. 95.
  66. 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)) } } } }
  67. 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)) } } } }
  68. 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)) } } } }
  69. 99.
  70. 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)) } } } }
  71. 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)) } } } }
  72. 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)) } } } }
  73. 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)) } } } }
  74. 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)) } } } }
  75. 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)
  76. 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)
  77. 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)
  78. 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) }
  79. 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) }
  80. 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)" } }
  81. 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)" } }
  82. 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)" } }
  83. 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)) } } } }
  84. 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)) } } } }
  85. 120.
  86. 121.
  87. 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)
  88. 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>
  89. 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)" }
  90. 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)" }
  91. 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)" }
  92. 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)" }
  93. 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)" }
  94. 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)" }
  95. 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)" }
  96. 131.
  97. 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>
  98. 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>
  99. 136.
  100. 137.
  101. 138.

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

    tells me things are complete or an error has occurred."
  102. 139.
  103. 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)
  104. 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)
  105. 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)
  106. 144.
  107. 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)
  108. 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)
  109. 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)
  110. 148.
  111. 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) }
  112. 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) } }
  113. 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) } }
  114. 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) } }
  115. 155.
  116. 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)" }
  117. 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)" }
  118. 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)" }
  119. 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)" }
  120. 166.
  121. 167.
  122. 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)" }
  123. 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)" }
  124. 171.
  125. 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)
  126. 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)
  127. 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)
  128. 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)
  129. 181.
  130. 190.
  131. 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)") }
  132. 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)") }
  133. 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)") }
  134. 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)") }
  135. 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)") }
  136. 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)") }
  137. 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)") }
  138. 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)") }
  139. 199.
  140. 200.
  141. 201.
  142. 202.
  143. 204.
  144. 205.
  145. 206.
  146. 207.
  147. 208.
  148. 209.
  149. 210.
  150. 211.
  151. 214.
  152. 216.
  153. 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.
  154. 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
  155. 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.
  156. 226.
  157. 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
  158. 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