---
title: Ordered Collection Diffing
teaser: 'Calculating diffs in Swift just got a whole lot easier.

  '
tags: ios,swift,xcode
author: Patrick Montalto
published_on: 2019-05-20
---

Determining and applying differences between states often requires a lot of
error-prone, complex code. However, Swift 5.1 introduces ordered collection
diffing by way of
[SE-0240](https://github.com/apple/swift-evolution/blob/master/proposals/0240-ordered-collection-diffing.md).
This change adds native support for diffing and patching functionality for
various collection types. Many state management patterns can benefit from
this improvement, particularly applications with undo/redo stacks or those
which sync content to/from a service.

Let's examine a simple use case for one of the new methods introduced with
this change, `difference(from:)`. This method returns the difference needed to
produce a collection’s ordered elements from another given collection, as long
as the collections consist of `Equatable` elements. We can then `switch` over
the diff, handling the `remove` and `insert` cases as we wish.

```swift
let diff = newData.difference(from: data)

for change in diff {
  switch change {
  case let .remove(offset, element, associatedWith):
    // Do something with removal
  case let .insert(offset, element, associatedWith):
    // Do something with insertion
  }
}
```

Within the `.remove` and `.insert` cases we'll also have access to the
associated values `offset`, `element`, and an optional `associatedWith`. For
`.remove`, the `offset` value is the offset of the `element` to be removed in
the original state of the collection. For `.insert`, the `offset` value is
that of the inserted `element` in the final state of the collection after the
difference is fully applied. For both cases, `associatedWith` is the value of
the offset of the complimentary change, allowing us to track pairs of changes
at the same time. We won't concern ourselves with this optional value for now,
and instead focus on the `offset` value.

## Diffing with UITableViews

Let's explore the usefulness of this new method in regards to `UITableViews`.
In our demo application, we have a `UITableView` which contains cells of
numbers. We can pull to refresh the data source and update the
`UITableViewCells` upon receiving the new data. We'll use this new diffing
method in conjunction with `performBatchUpdates(_:completion:)` to insert and
delete the appropriate `UITableViewCells`. Since deletes are processed before
inserts in batch operations using `performBatchUpdates(_:completion:)`,
mirroring that of `difference(from:)`, there are no inconsistencies between
the indices generated by the `offsets` of our diff.

Note: In order to call `difference(from:)`, we're required to add an
availability check for [iOS 9999](https://github.com/apple/swift/pull/23071),
which in Swift 5.1 will always return true. This represents a future iOS
version that has not yet been released.

```swift
 private func fetchNewData() {
    // Simulate a two second long network request
    networkQueue.asyncAfter(deadline: .now() + 2) { [weak self] in
      if #available(iOS 9999, *) {
        DispatchQueue.main.async {
          guard let self = self else { return }
          var deletedIndexPaths = [IndexPath]()
          var insertedIndexPaths = [IndexPath]()
          let newData = self.simulateNewData()
          let diff = newData.difference(from: self.data)

          // Gather the the index paths to be deleted and inserted via the diff
          for change in diff {
            switch change {
            case let .remove(offset, _, _):
              deletedIndexPaths.append(IndexPath(row: offset, section: 0))
            case let .insert(offset, _, _):
              insertedIndexPaths.append(IndexPath(row: offset, section: 0))
            }
          }

          self.data = newData

          self.tableView.performBatchUpdates({
            self.tableView.deleteRows(at: deletedIndexPaths, with: .fade)
            self.tableView.insertRows(at: insertedIndexPaths, with: .right)
          }, completion: { completed in
            self.refreshControl.endRefreshing()
            print("All done updating!")
          })
        }
      }
    }
  }
}
```

Aside from using `difference(from:)`, it's useful to call
`performBatchUpdates(_:completion:)` when you want to make multiple changes to
the table view in one single animated operation. This is beneficial over just
calling `reloadData`, which can unnecessarily reload all cells, headers, and
footers (even those which have not changed). Also, `reloadData` won't allow
granular control or provide the ability to animate the changes.

Now, pulling to refresh and fetch new data will update the table view by
fading out the deleted rows and inserting the new ones from the right:

![Updating Table View With Animations](https://images.thoughtbot.com/blog-vellum-image-uploads/bGeiaiDrQZilL2kMRAJh_OrderedCollectionDiffing1.gif)

## Wrap Up

Swift 5.1 brings many improvements to the language, and native support for
ordered collection diffing is a much welcomed addition. This could allow
projects which had previously depended on third-party libraries, or other
potentially error-prone diffing code, to use this native implementation
instead.

For the full example project, check out the repository
[here](https://github.com/thoughtbot/ordered-collection-diffing). You'll need
to be sure you're running a version of Xcode which uses a Swift 5.1 toolchain
in order to build the application.
