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

The Great iOS Concurrency Bake-Off - Swift User...

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

Ellen Shapiro

February 28, 2019
Tweet

More Decks by Ellen Shapiro

Other Decks in Technology

Transcript

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

    NL ! | Amsterdam | February 2019 @DesignatedNerd | bakkenbaeck.no | justhum.com
  2. !

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

    another asynchronous function (and another and another)
  4. As a developer, I want a list of profiles with

    icons to not drop frames when scrolling.
  5. 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)
  6. 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..." })
  7. 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..." }) })
  8. ...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))!" }) }) })
  9. 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))!" }) }) })
  10. 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))!" }) }) })
  11. 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))!") }) }
  12. 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))!") }) }
  13. 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))!") }) }
  14. 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))!" }) }) })
  15. 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) }
  16. 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)
  17. 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)
  18. 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)") } }
  19. 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)") } }
  20. 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)") } }
  21. 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)") } }
  22. How do we chain operations which need each others' results

    so we can figure out where something's going wrong?
  23. 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)) } } } }
  24. 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)) } } } }
  25. 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)) } } } }
  26. 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)) } } } }
  27. 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)) } } } }
  28. 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)) } } } }
  29. 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)) } } } }
  30. 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)) } } } }
  31. 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)
  32. 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)
  33. 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)
  34. ! 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) }
  35. ! 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) }
  36. 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)" } }
  37. 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)" } }
  38. 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)" } }
  39. 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)) } } } }
  40. 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)) } } } }
  41. 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)
  42. 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>
  43. 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)" }
  44. 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)" }
  45. 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)" }
  46. 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)" }
  47. 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)" }
  48. 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)" }
  49. 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)" }
  50. 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>
  51. 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>
  52. Observable "I could get many values from this, until it

    tells me things are complete or an error has occurred."
  53. 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)
  54. 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)
  55. 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)
  56. 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)
  57. 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)
  58. 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)
  59. 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) }
  60. 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) } }
  61. 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) } }
  62. 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) } }
  63. 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)" }
  64. 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)" }
  65. 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)" }
  66. 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)" }
  67. 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)" }
  68. 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)" }
  69. 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)
  70. 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)
  71. 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)
  72. 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)
  73. 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)") }
  74. 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)") }
  75. 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)") }
  76. 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)") }
  77. 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)") }
  78. 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)") }
  79. 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)") }
  80. 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)") }
  81. 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.
  82. 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
  83. 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.
  84. 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
  85. ! 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