Migrating Asynchronous Code to Combine
Tags: swift, combine, reactiveprogramming
Published: 2020-09-03 00:30:46

Allow me to set the stage before we jump in.

Let's say we have an app that shows a list of cells displaying an animal name and two buttons: one to show the animal emoji and the other to make the sound of that animal.

![App Screenshot](https://www.kiloloco.com/images/migrating-to-combine-device-screenshot.png)

> Designer was paid in equity 😛

Just like any real world app, we have similar features that have been implemented differently because everyone on the team seems to think their way is best 🤡

```swift
/// AnimalsViewController.swift

class AnimalsViewController: UITableViewController {
    ...
    
    func getAnimals() {
        NetworkingService.getAnimals { [weak self] (result) in
            switch result {
            case .success(let animals):
                self?.animals = animals
                self?.tableView.reloadData()

            case .failure(let error):
                print(error)
            }
        }
    }
    
    ...
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        ...
        
        animalCell?.delegate = self
        animalCell?.shouldMakeNoiseForAnimal = { [weak self] animal in
            self?.makeNoise(for: animal)
        }
        
        ...
    }
    
    ...
}

extension AnimalsViewController: AnimalCellDelegate {
    func shouldShowEmoji(for animal: Animal) {
        showEmoji(for: animal)
    }
}
```

This `TableViewController` is responsible for retrieving the `Animal`'s from a server and implementing the logic for when a user taps on either the "Show Emoji" button or the "Make Noise" button.

```swift
/// NetworkService.swift

enum NetworkingService {
    static func getAnimals(completion: @escaping (Result<[Animal], Error>) -> Void) {
        let animals: [Animal] = [.dog, .cat, .frog, .panda, .lion]
        completion(.success(animals))
    }
}
```

When calling the networking service, we pass in a closure that acts as a callback for when the server has returned the `Animal` data.

```swift
/// AnimalCell.swift

protocol AnimalCellDelegate: AnyObject {
    func shouldShowEmoji(for animal: Animal)
}

class AnimalCell: UITableViewCell {

    ...
    
    weak var delegate: AnimalCellDelegate?
    var shouldMakeNoiseForAnimal: ((Animal) -> Void)?
    
    @IBAction func didTapShowEmojiButton() {
        delegate?.shouldShowEmoji(for: animal)
    }
    
    @IBAction func didTapMakeNoiseButton() {
        shouldMakeNoiseForAnimal?(animal)
    }
    
    ...
}
```

The `AnimalCell` is where we have two different ways to do the same thing; pass the `Animal` object when a button is tapped. The first way is in `didTapShowEmojiButton` which is using the delegate pattern to send the  `Animal` object to our `AnimalsViewController`. The other is in `didTapMakeNoiseButton` which is using a callback to send the animal to `AnimalsViewController`.

So as we can see, we have a few examples of asynchronous code in this app. One important difference to understand between these types of communication is that the "networking request" that we are performing is only going to give us a single result. Either we will end up with a success and get our array of `Animal`'s, or we will get an error, and that's it. 

If the server is down, and sends an error, but one second later it is up and **can** send the array of `Animal`'s, it wont do so until another request is made. It is in these circumstances where we are only expecting a single result to come back, that we want to work with `Future`.

### Futures

`Future` is a publisher that is designed to return the result of a `Promise`. A `Promise` is almost identical to a callback with a `Swift.Result`, you write some control flow, then return a `Promise` by doing something like `promise(.success(myObject))`. `Future` is initialized with a `Promise` closure and then is treated like any other `Publisher` where you must use a `sink` to get the value.

Let's take a look at this in action by replacing our `getAnimals` method in `NetworkingService.swift`:

```swift
... // enum NetworkingService {

static func getAnimals() -> Future<[Animal], Error> {
    return Future { promise in
        let animals: [Animal] = [.dog, .cat, .frog, .panda, .lion]
        promise(.success(animals))
    }
}

... // NetworkingService closing }
```

There is clearly a lot of similarity between our former function signature with a callback and our new function signature that returns a `Future`. We created our `Future` by passing in a closure that takes a `Promise`, and we pass either a `success` or a `failure` to our promise once we have determined the correct result to return; in this case, `success(animals)`.

Back in our `AnimalsViewController` we are going to start getting errors since the function signature of `NetworkingService.getAnimals` has changed. To fix these errors, we simply handle our newly returned `Future` just like we would handle any other `Publisher`.

```swift
...

var getAnimalsToken: AnyCancellable?
func getAnimals() {
    getAnimalsToken = NetworkingService.getAnimals()
        .sink(
            receiveCompletion: { completion in
                switch completion {
                case .finished:
                    print("Subscription finished")
                    
                case .failure(let error):
                    print("Error getting animals -", error)
                }
            },
            receiveValue: { [weak self] animals in
                self?.animals = animals
                self?.tableView.reloadData()
            }
        )
}

...
```

So now we have a `sink` for our `Future` `Publisher` which takes in two closures: `receiveCompletion` and `receiveValue`. We use `receiveCompletion` to handle any errors that may have occured as well as do any additional logic once the subscription has finished. The `receiveValue` block is more straightforward by simply passing in the expected value; in our case, an array of `Animal` objects.

Another thing we need to keep in mind is that `sink`'s will fall out of memory if we don't keep a reference to them. That's where our little buddy `getAnimalsToken` comes in.

### PassthroughSubjects

The difference between the "networking request" and the button taps coming from our cell is that there can be an infinite number of taps/values that are passed down stream from our buttons. When we are working with multiple results and don't know if/when they will stop coming, we need to use `PassthroughSubject`. 

Unlike `Future`, the  `PassthroughSubject` subscription will not finish after the first result is sent, but instead requires that a `Completion<Failure>` is manually sent down the stream if we want it to finish.

Let's take a look at this in action by replacing our delegate and callback code in `AnimalCell.swift` with `PassthroughSubject`. 

```swift
...

var showEmojiPublisher = PassthroughSubject<Animal, Never>()
var makeNoisePublisher = PassthroughSubject<Animal, Never>()

@IBAction func didTapShowEmojiButton() {
    showEmojiPublisher.send(animal)
}

@IBAction func didTapMakeNoiseButton() {
    makeNoisePublisher.send(animal)
}

...
```

It's a pretty straightforward swap. We replace the `delegate` and `shouldMakeNoiseForAnimal` properties with `PassthroughSubject`'s that can be subscribed to through `sink`'s at the call site. Then we simply send the `Animal` through the respective `Publisher` whenever either button is tapped.

While replacing our two original forms of communication with two `PassthroughSubject`'s would work, it is less than ideal because we would need to hang onto a reference for both `sink`'s in every cell. It's also kinda smelly cuz they're the same type of `Publisher` with different names.

Let's create a nested `Action` enum that we can send to the `Publisher` while still maintaining context as to what should be happening when each `Action` comes down stream.

```swift
...

enum Action {
    case showEmoji(Animal)
    case makeNoise(Animal)
}

var actionPublisher = PassthroughSubject<Action, Never>()

...

@IBAction func didTapShowEmojiButton() {
    actionPublisher.send(.showEmoji(animal))
}

@IBAction func didTapMakeNoiseButton() {
    actionPublisher.send(.makeNoise(animal))
}

...
```

Our new `actionPublisher` will publish `AnimalCell.Action`'s and now we only have one `sink` to hang onto for each cell in our table view.

Time to update `cellForRowAt indexPath` in `AnimalsViewController.swift`:

```swift
...

// 1
var cellTokens = [IndexPath: AnyCancellable]()

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    ...
    
    // 2
    cellTokens[indexPath] = animalCell?.actionPublisher
        .sink { [weak self] action in
            // 3
            switch action {
            case .showEmoji(let animal):
                self?.showEmoji(for: animal)
                
            case .makeNoise(let animal):
                self?.makeNoise(for: animal)
            }
        }
    
    ...
}

...
```

1. `cellTokens` will allow us to keep a reference to each `sink` so it stays alive and can observe values.
2. We set the resulting `sink` to the associated `IndexPath`.
3. Switching on the `action` allows us to handle both our `AnimalCell.Action`'s, and acts as a reminder to update our `sink`'s logic if we do add another `Action` in the future.

Our app should now continue to work as it did before, and now **our**   way is best. 

Welcome to **10x Engineer** status 🙌🏽

### Conclusion

Combine is pretty slick, and offers us an opportunity to consolidate all of our communication patterns into a single pattern. Also, everybody and their mom is using reactive frameworks, so might as well get comfortable with the first party version as it is likely to become the standard for native iOS development.

Now go out there, convince your boss that you need to refactor all your code, and do so passionately 😘