[ Music ]
[ Cheering and Applause ]
My name is Steve Breen, and I'm an engineer on the UIKit Team.
And today, I'm joined by my colleagues, Troy Stevens from the AppKit team, and Jacob clapper from the App Store team.
And today, we're going to talk a bit about UI data sources.
[Chuckles] All right, so today's talk is going to be broken into four segments.
First, I want to talk about the current state-of-the-art.
How do we interact with data sources today in the shipping platforms?
And then we're going to talk about a brand new approach we're bring into iOS, tvOS, and the Mac.
Then we're going to transition to some demos, get some hands-on time with this brand new API.
And then finally, we're going to go on some more detailed considerations of how to get the best out of this new API.
All right, so let's talk a little bit about the current state-of-the-art.
How do we interact with UI data sources today, in UITableView and CollectionViews?
So here we see an example implementation of the UICollectionView data source.
Now, if you've worked with UITableView or CollectionViews, you've seen this before.
Here, we're providing three of the required two methods in this protocol.
And it's pretty straightforward, right?
We have asked about the number of sections and the number of items in each of those sections.
And as content renders, we're going to ask for cells.
And this has served us really well for about ten years now, right?
And it's got a couple things going for it.
It's super simple.
You can reason about it right away.
If you want to provide just two methods, the number of items in a section, if you have a one-dimensional data source, that's pretty straightforward.
And you can iterate on it pretty quickly.
But it's also very flexible, right?
Because you don't really have to use any particular kind of data structure to back your data source.
It could be as simple as a one-dimensional array.
And if you had a multi-itemed, multi-sectioned data source, it could be two-dimensional, right?
Very simple, very straightforward.
Well, apps are often a little more complicated than a, you know, a one-dimensional or two-dimensional array.
And apps get more and more complex every year.
They do more things.
Our users demand more features.
And oftentimes, these data sources are backed by complex controllers inside of our app.
And these controllers can do a variety of things.
They can interact with Core Data, and they could talk to web service.
They just do a number of different things.
And I want to visualize this real quick.
And let's show a conversation between your UI layer and this controller layer that's doing all this heavy lifting to get your data.
And the conversation starts out very civil.
It's like, "Hey, give me a number of items in a section, or give me a cell as we render content."
And so far, smooth sailing.
But things get more complicated over time, right?
You get like let's say this controller has a web service request that it gets a response from, right?
It's like, oh, I've got data for your tweets or whatever, right?
Well, now this controller layer, which is complex unto itself, lets the world know, "Hey, I changed.
All right, so here's where things get a bit more complex, right?
So now, it's up to the UI layer to decide, "Hey, things changed.
Now, I need to can change this into updates for our UI layer.
And this involves all the mutations that have to occur against TableView and CollectionView.
It can be a bit complex.
And we covered this complexity last year in our talk Of Tour of UICollectionView.
And how to construct those batch updates properly, and mutate your backing store, all those kinds of things.
But sometimes, no matter how hard you try [ Laughter ]
You know, things go wrong.
It's an imperfect world.
And judging by the laughter, you've probably hit this before, right?
It's not an uncommon thing.
And it's really frustrating, right?
You hit this, and you're like, "All right.
What did I do wrong?"
It's me. And as you dig through your code, okay, fine.
You Google on Stack Overflow, see what's going on.
And eventually, you might get frustrated and just call reloadData.
And we talked about this last year, and that's fine.
Your app looks okay.
But when you call reloadData, you get this non-animated effect.
And it detracts from the user experience.
So that's not great.
Okay, I want to get philosophical just for one slide.
What's the problem?
Well, the problem here is where's our truth?
You know? I mean, who got his?
And who has all the answers?
And the big issue here is that our data controller or it's acting as a data source has its own version of the truth, which changes over time.
And the UI has a version of the truth.
And the UILayerCode is responsible for mitigating that, making sure that it's always in sync.
As we saw, it's sometimes hard.
So our current approach is error prone.
And primarily because there's just no notion of a centralized truth.
All right, so that's the state-of-the-art.
That's where we are today.
But where are we going?
Well, I'm happy to announce that for iOS, TVoS, and MacOS this year, we're bringing a brand new approach.
And we're calling this DiffableDataSource.
[ Cheering and Applause ]
All right, so let's dig in.
What is this thing?
Let's go on.
And along with it, all the crashes, hassles, complexity, all of the stuff that you don't want to deal with, has been jettisoned.
Instead, we have a single method we call Apply.
What is Apply?
Apply is simple, automatic, hassle-free diffing.
So we do this with a brand new construct we call a Snapshot.
And it's a very simple idea.
It's effectively the truth of the current UI state.
And instead of IndexPaths, it's an association or a collection of section identifiers that are all unique and item identifiers.
And you update these not with IndexPaths, with identifiers.
All right, let's look at something visual to see what's going on here.
Okay, I'm really creative guy.
So we've got these FOO, BAR, and BIF onscreen, right.
And that's what we're interacting with.
These are identifiers in our app.
And let's say that our controller changed.
And now we've got this brand new Snapshot that we want to apply.
But this is our current Snapshot.
How do we get from our new truth to the current Snapshot?
Well, we can see here we've configured a brand new Snapshot with BAR, FOO, and BAZ.
And we have some items that are coming along for the ride that have just changed order, and then a new item coming in.
So conceptually an Apply knows about the current state and knows about the new state, which are going to apply to the UI element.
And there's no step two.
That's just it.
Mm-hmm. All right, so how do we do this?
Well, we have four classes across all the platforms.
For iOS and TVoS, we have UICollectionView DiffableDataSource and UITableView DiffableDataSource.
And then on the Mac, we have NSCollectionView DiffableDataSource.
And common for all the platforms is this Snapshot class, which is responsible for the current UIState NSDiffableData SourceSnapshot.
Okay, so enough background.
Let's go on, look at some code.
And for that, I'm going to bring up my colleague, Troy Stevens.
[ Applause ]
Thanks very much, Steve.
I am delighted to get to walk you all through some examples of using this powerful, yet beautifully simple new API today.
So make sure to download the sample project for this talk if you haven't already.
That way, you can follow along, study it at your leisure.
And most importantly, use that example to really grasp the mechanics of how all of this works.
It's not a lot of code.
It's really simple.
Now, when you look at that example project, you're going to notice that in addition to the three examples of DiffableDataSource usage that we're going to look at today, the same project also contains illustrations of the powerful, new compositional layout API that we introduced in Session 215.
Those examples just happened to use DiffableDataSource as a really easy way to just populate their CollectionViews with sample content.
So let's go to our demo machines and take a look.
So I've got our demo app up here.
And as we walk through our different examples today, we're going to notice a repeating pattern.
It's a simple three-step process.
So anytime you want to put a new set of changes, a new set of data into a Collection View or UITableView with the full data source, all you do is you create a Snapshot.
You populate that Snapshot with the description of the items you want to display in that update cycle.
And then you apply the Snapshot to automatically commit the changes to your UI.
DiffableDataSource takes care of all the diffing and issuing the changes to the UI element.
So let's look at a concrete example.
I'm going to open up Mountain Search.
And this is a fairly typical search UI, right?
We can all look at it and reason about what it does.
You might see this in a Contacts app, for example, but in this case, we're looking at mountain peaks around the world.
So as you can imagine, there's a search field at the top.
As I start typing in that search field, we would expect to see the list automatically filtered to just the matches.
And we'll see that if I start typing, we can do just that.
And it all happens very automatically and with nice animations.
This is all incredibly easy to do with extraordinarily little code using DiffableDataSource.
So let's take a look at how it works.
For this example, look at the MountainsView ControllerSource file.
And all the action starts when the user types in the search bar, right?
So we have our callback here, searchBarTextDidChange.
It's going to be sent to our controller.
And from there, we just call out to our own performQuery function passing it, the search text that we got from that search bar.
Now performQuery itself is remarkably simple.
All we do is call out to our MountainsController.
That's our model layer object.
And we ask it for a filtered sorted list of the mountains that match our search term.
So now we've got that list of mountains.
We go through that three-step process that I mentioned.
We create a new NSDiffableData SourceSnapshot.
Now this Snapshot is initially empty.
It contains nothing.
So it's up to us to populate it with the sections and items that we want.
In this case, we only have a single section to display.
So we're just going to append one section, and we're just going to arbitrarily call it the main section.
Next, we append the identifiers of the items that we want to display in this update.
Now formally speaking, we usually pass an array of identifiers here.
But in Swift, you can also make things a lot more elegant by working with your own native types here.
So if you have a native type, and it can even be a value type, such as a struct or an enum, if you make that type hashable, then you can just pass your own native objects in terms of the Swift syntax of what you're doing.
And we'll look at the implications of how that works in just a little bit.
So now, we've constructed our Snapshot.
It's ready to go.
All we have to do is call to our DiffableDataSource, ask it to please apply that Snapshot, animating the differences.
DiffableDataSource goes off and automatically figures out what's changed between the previous update and the next.
Notice that there's no code at all here, where we had to stop and figure out reason about what we were displaying in the previous update cycle before the user type.
That one more character, it all happens automatically.
There's nothing to worry about.
We're not dealing with IndexPaths, which are fragile and ephemeral, right?
They refer to a certain particular update, and have a different meaning and a different update.
We're dealing with identifiers that are robust and enduring.
So everything is just really simple to do here.
Before we leave this bit of code, I want you to notice something about Snapshot, which you may have already noticed.
It is a generic class in Swift, so it's parameterized by the SectionIdentifierType and the ItemIdentifierType that we've decided to use.
So let's look first at our SectionIdentifierType, which is pretty trivial.
This is a really handy technique for the common case, where you just have a single section, right?
You can just declare an enum type for this.
And one nice thing about enums in Swift is that they're automatically hashable.
Hashability is synthesized for them.
So here, we just have a trivial case of an enum with one case, and there's nothing more to do there.
For our mountain type, we'll look at our Mountains Controller, which is again, our model layer.
And here, we see that we've declared mountain as a Swift struct.
And we declared that struct type as hashable so that we can use it with DiffableDataSource natively rather than explicitly have to pass an identifier.
And the important requirement there is just that each mountain be uniquely identifiable by its hash value.
So we achieve this by giving each mountain an automatically generated unique identifier.
And here, where we implement that hashability conformance that we promised, we use just that identifier to provide the hash value.
So in this way, we're going to have each mountain, even though mountains, again, are value types.
They just get passed around by copying.
There's no pointer you can reference.
But the identifier and the hash value specifically of that identifier makes them unique enough for DiffableDataSource to track them from what one update to the next.
And as part of hashability, we're implementing equality tests here, too.
So we've looked at how you issue changes to DiffableDataSource.
What about how we set one up to use?
Let's go back to our MountainsViewController.
And conveniently enough, we've created a function called ConfigureDataSource, where we configure our data source.
And it's really just a little bit of code here.
So in this case, we're working with a UICollectionView.
So we instantiate a UICollectionView DiffableDataSource.
We parameterize it with our section and item types.
Pass it a pointer to the CollectionView that we want to work with.
Now DiffableDataSource will take that pointer and automatically wire itself up as the data source of that CollectionView.
So there's nothing else for us to do.
Lastly, we have this trailing closure parameter, the DiffableDataSource to the initializer.
And all this is, is the code that you would normally have to write if you were implementing your own data source from scratch, you would implement the cellForItemAt IndexPath method.
That's data source callback method, where you're expected to do exactly what we do here.
We call back to our CollectionView and ask it for a cell of the appropriate type to display the data we want to.
And we populate that cell with what we want to show, and then we return it back.
So this is just taking that cellForItemAt IndexPath code and conveniently transplanting it into a nice closure encapsulation that we pass when we instantiate the data source.
One thing that's nice and convenient and different here is that in addition to being given the IndexPath of the item that we're being asked for, we're also given its identifier, or in this case, the native Swift value type that we that corresponds to that particular item we want to show.
So we get our mountain passed in, there's no more work to do.
We don't have to go take that IndexPath and go look up which of our model layer objects it pertains to.
We just have our mountain passed in for us.
So we just can get the name of the mountain and set it as the label text of that cell.
And that's all there is to it.
Everything else about how you set up and configure your Collection View is the same as before.
There's no performBatchUpdates code hiding anywhere in this sample code.
That's all there is to it.
It's very easy.
Let's look at another example.
So here, we have a mockup of the familiar Wi-Fi Settings UI from iOS.
And this one's only going to be slightly more complicated than the last because we have two different sections that we're working with here.
We have what we call the Config section at the top, where we have the Wi-Fi enable/disable switch.
And the display of the current network that we're connected to.
And then below that, we have another section that's dynamically updating that shows us a list of detected networks that we could potentially connect to.
And another thing to notice here, if we tap the Wi-Fi disabled switch, or toggle it back on, we get a nice, animated, collapse or reexpand of our UI.
And that all comes for free with DiffableDataSource.
So let's take a look at how this dynamic UI is implemented.
Going to our WiFiSettingsViewController, we're going to look at the updateUI function, which is just a function we've named updateUI.
And we've taken care to make sure that this gets called anytime there's a change in what we need to display.
Now most of the time, that might be because we've detected a different set of networks that's now available.
But it could also be because the user has toggled that Wi-Fi enable/disable button.
Anything that changes our UI, we've ensured that this will be called.
So we still have here that same three-step process.
After getting the data that we want to display, we start with just getting those config items, because we're usually going to want those.
We go ahead and create a Snapshot.
And this Snapshot again is initially empty.
So let's populate it with what we want to show.
We append the first section, our config section, that appears at the top.
And we append its items.
So there's going to be one or two items depending whether Wi-Fi is enabled.
Now if Wi-Fi is enabled, we also want to talk to our backend, to our model layer.
Ask it for lists, the current list of available networks.
And we're going to wrap that list in some item types that we're going to look at in just a moment.
We append this section for that list of networks.
And then we're going to append the items that go in that section.
And notice that because we're working with two different sections here, we can be explicit about which section were appending each set of items to.
We're ready to go.
We've described everything we want to display.
So we just ask our DiffableDataSource to apply those changes, optionally animating the differences.
Now there may be times where you want to choose not to animate the differences.
For example, when you're first bringing up your UI, and you're showing the initial set of data, you might or might not want it to animate.
Oftentimes, you want it to be instantaneous.
So you'll pass false for animating differences, as we do in this example.
Looking at our types that we're working with here, we've got a section type and then item type that parameterize the DiffableDataSource.
Looking back at the top, we can see, as we might imagine, that section is still an enum type.
We just need two different sections to work with here.
And again, section as enum type is automatically hashable in Swift, so we're good to go with that.
And then here we've declared an item type.
This is a struct type again, as with the mountains.
And we've declared it as hashable.
And the reason for declaring this type is that when we look at our list, it mostly contains lists network items in it.
But in addition to that, it's got this oddball item at the top.
It's the Wi-Fi enable/disable switch.
That's not a network items.
So we have a heterogeneous list here.
And all we're doing is encapsulating each item in this generic wrapper type.
But that wrapper type, since that's the type of item we're going to hand off the DiffableDataSource, we have to take care to make sure that it conforms to hashable and that the items are uniquely identified by their hash value.
So for the network items, we can just get that unique identifier from the network item and transplant it into the capital item.
And for the config items, we just dynamically generate a UUID.
Looking down here at our hash function, again, it's just computing a hash based on that unique identifier value.
And that's all it takes for DiffableDataSource to be able to identify the items that are the same from one update cycle to the next.
Let's look at where we configure our data source.
This is really very similar to before, except this time, we're actually working with the UITableView.
And from the perspective of creating and committing Snapshots, it really doesn't matter, the API is very similar.
But for the setup, we've got to instantiate the right type of DiffableDataSource.
So we got a UITableView DiffableDataSource.
We parameterize that class name with the section and item types that we're going to use.
And we pass a pointer to the table view, which again, gets automatically wired up with this DiffableDataSource as its data source.
Lastly, we've got that trailing item provider closure.
And this looks more complex at first glance, but really, it's only that way because we have a variety of different types of items.
We have those heterogeneous items to display.
Basically three types of items, and we're handling them differently.
But this code, even if we weren't using DiffableDataSource, it would be there.
It would just be in your cell for ItemIndexPath method.
So again, very simple to set up this UI.
And even configuring the DiffableDataSource is not that hard.
So our last example is maybe the most fun to watch.
Here we've got a UICollectionView again that is displaying items that are represented as color swatches.
And they're initially in a randomized order, color wise.
If I tap the Sort button, we can watch them be iteratively sorted into spectral order.
So in addition to being really mesmerizing and fun to watch [chuckles]
[ Applause ]
Credit to Steve on this one.
In addition to being really mesmerizing, and fun to watch, this example is just a little bit different than the others in the way that we construct and commit the updates.
So if we wanted to, if we were just if our goal was just to sort everything and jump to the end state, we could do that here, right?
This demo is set up so that we can watch the sort progress iteratively through each intermediate stage.
So in order to do that, we've got our sword implementation that goes through the stages and gives them to us one at a time.
It gives us each successive new state, and we create a Snapshot and apply a Snapshot each time that happens, each time we do an update cycle.
And that gives us this nice, fun, animated view.
So let's look at how we implement this and how it's different.
We're going to look at the InsertionSortViewController here.
And all the interesting action happens in this PerformSortStep function.
So as I said, we always have that three-step cycle.
We're going to get a Snapshot, populate it, and then apply it.
But in this case, instead of asking for a new, empty Snapshot, we're going to take advantage of the ability to ask our DiffableDataSource for its current Snapshot.
Now this Snapshot is prepopulated with the current truth of what's being shown in that UICollectionView as the CollectionView sees it.
So we don't have to start over from scratch.
We can start from that state and compute the next intermediate state.
Down here, where we populate our Snapshot, we'll see that familiar AppendItems function being called.
But we also have a DeleteItems function here.
And when you look at the Snapshot API, you're going to see there are whole variety of functions for modifying an existing Snapshot for when you're doing this kind of usage.
You can move items from one place to another, and so on.
But in other respects, this is pretty much the same thing.
We're just trying to set up the new final state of what we want to display.
And we're just working with identifiers and not IndexPaths, which is really nice.
So lastly, when we're done, all we have to do is apply that Snapshot to our DiffableDataSource.
And we get this nice, progressive sorting.
So pretty cool.
[ Applause ]
[ Applause ]
And here's where we set up our DiffableDataSource.
Again, it's for a UICollectionView.
We specify the types that we're using, the CollectionView, and then we have our item provider closure, which is real simple, because we're just displaying these color swatches.
So we've seen, through these three examples, how easy it is, and how little code it takes to create these dynamic UIs that are also really robust to changes.
We can make changes without fear of hitting these weird exceptions in our code.
It's all very robust.
It's baked into the API how that works.
Now, we did touch on some interesting nuances, and the importance in particular of uniquely identifying your objects.
And if you're working with Swift types, what those Swift types need to conform to and so on, that they need to be hashable.
To take us on a deeper dive and bring these issues into clear focus.
I'm going to invite my colleague, Steve, back on stage.
[ Applause ]
And so I kind of have a sense for how this UI works walking through all these demos.
I want to go through some more detailed considerations of how to get the best out of this API.
First, as we've seen all throughout the demos, there's basically just three steps.
You want to create a Snapshot, configure it as you need it, and apply it.
So you always want to call the apply method.
Now conversely, you don't call performBatchUpdates anymore.
That's old and dead.
Called insertItems, none of that.
If you call these, the framework will complain.
And, you know, you will see that.
Okay, there are two ways to create Snapshots.
And the most common way is to create an empty Snapshot.
And here we see, we construct the Snapshot with the types for the sections and items.
And you can also create one, like we saw in the last demo, from the current state.
And this is very useful sometimes when a certain action occurs where you need to modify just one little thing.
Now when you create this, you're going to get a copy back.
So you can mutate that at will, and it will not affect this the data source that it came from.
Now once you have that Snapshot, if you did ask some questions, like, you know, "How many items you got?
How many sections you got?
Identify the identifiers."
You can do that.
And there's a lot of API you can check out in the SDK.
But here's a few of those.
All right, so I promise you no more IndexPath.
So when we configured these Snapshots, you'll never see an IndexPath in this API, through explicit API.
So, so far, we've seen a very common pattern of appending items and appending sections, all those kinds of things.
But you can also do things like insert and move and delete.
And all of these API's take in other relative identifiers to tell you where things go.
So if I want to insert 20 new identifiers that are all unique, before or after another identifier, we have explicit API for that.
So you'll say, "Insert these identifiers before this identifier."
Now, if you don't have anything in that particular section, there's no identifier to anchor that insertion or move with, that's why we have the append APIs.
You can append items and sections.
Now, in that familiar path, where you have a number of sections, where you're configuring your Snapshot, you might loop through your section data.
And in that instance, you can append items without specifying the section.
There's a default parameter in Swift that specifies to null.
In this instance, we'll just append to the last-known section.
So it makes that code very pretty.
All right, so let's talk a bit about identifiers.
These have to be unique.
And this isn't a big problem, because most apps have some kind of notion of identity in their model objects.
So it's a very natural step to just use that unique identifier.
Now in Swift, this needs to conform to hashable.
And conveniently enough, many things in Swift do this automatically.
We saw auto synthesis for this in the enum types.
And we have string, and integer, and UUID, all these great things that are available for use for DiffableDataSource.
Now, we also saw that you can bring in some model data into these identifiers.
And this is really convenient.
Now your identity needs to come from some unique identifier.
But you can also bring in other attributes.
When we saw like the name and the mountains, you can control our example.
And this is really handy, because when you configure your cell, you have everything you need right there in line.
No looking it up somewhere else.
All right, here's a little, quick template that we see throughout the examples that talks about how to create a hashable thing in Swift where they're using a struct.
Pretty straightforward stuff.
All right, so what about IndexPath-based APIs?
Okay, so we have CollectionView and TableView.
They have tons of IndexPath-based APIs.
A lot of them in the Delegate methods.
So if a user interacts with the content and taps on an item, you'll get this familiar Delegate message didSelectItemAt IndexPath.
But we've moved into this great new identifier-based world.
What are we going to do with this IndexPath?
You know, that's old school.
So here we have new API's.
Let you translate between identifiers, IndexPaths, and then back from IndexPaths to identifiers.
So here, we see an example.
We're taking that identifier, that IndexPath that's past, and converting it back to an identifier.
And this is constant time.
This is super, super fast.
All right, so speaking of performance.
So we've done a ton of work to make this as fast as possible.
And there's a lot of really great low-level stuff that just blazes.
Now, if you've ever studied in the computer science, the whole notion of how diffs occur, you know that this is a linear operation, O of N.
And in simple terms, all it just means is the more items you have, the longer your diff takes.
So during development, it's super important to measure your apps.
We all know this.
We want to make sure that the main queue is always as free as possible to be real responsive to user events.
And we render everything really quickly.
So as you're measuring your app you all measure your apps during development, especially towards the end.
I want to make sure that everything's really free on that main queue.
Well, if you find that you have a large number of items in that linear diffs taking that little extra time, it is safe to call the Apply method from a background queue.
[ Applause ]
Now, what's really cool about this is the amount of API we have to support this.
There's no API.
[ Laughter ]
Which is the best API.
[ Cheering and Applause ]
All right, so what happens if you call Apply from the background queue?
Well effectively, the framework is smart enough to know, "Hey, I'm on the main queue."
And it says, "Let's just keep doing this diff right here."
And once that diff is computed, we jump back to the main queue, apply the results from our diff, and life goes on, like normal.
All right, so just one caveat, and I promise, just one.
If you choose this model to call Apply from the background queue, be consistent.
Just always call it from the background queue.
You never want to mix and match calling it from a background queue or the main queue.
Just always do it the same way.
And we're good citizens.
We'll complain about this if you get it wrong.
All right, so at Apple collaboration is a big part.
It's the main strength of our organization, the way we talk to each other and solve problems together.
And part of this as the frameworks authors is to make sure that all of your clients, or who you talk to on a regular basis, and find out what they're struggling with.
And this clearly is one of the things they're struggling with.
So during this, we chatted with the folks who are working on the Share Sheet, this new, redesigned Share Sheet in iOS 13, with the great airdrop extensions.
And they found out about it kind of late in the game when they had this brand new design.
And they said, "Oh, this looks great.
We need this."
And indeed they did.
So I want to bring up one of my colleagues from the Share Sheet team, Jacob Clapper, to walk us through that adoption.
[ Applause ]
I'm really excited to show you how the Share Sheet has been able to take advantage of the great new CollectionView APIs in iOS 13.
All right, so here we are in the brand new Share Sheet.
And this Share Sheet actually takes advantage of the new compositional layout API's and DiffableDataSource.
But where DiffableDataSource really shines is in the brand new airdrop extension.
So the airdrop extension has a browser that's browsing for devices.
And we actually already use UUIDs to uniquely identify each device that's discovered.
So as new devices are discovered, we are able to create an empty Snapshot, append our sections and items, and Apply the differences.
DiffableDataSource takes care of the rest.
And the animations are beautiful, no matter how many items are removed or deleted.
DiffableDataSource has been a game changer for us.
And we can't wait to see what you do with it in your apps.
I'm going to hand it back over to Troy for some final thoughts.
[ Applause ]
Boy, it's so exciting to me to hear how much DiffableDataSource is already making a difference in the development of our own apps.
And we couldn't be more thrilled to make this same API that we've been adopting internally available to use for developers across our platforms.
So as we've seen today, DiffableDataSource dramatically simplifies the work that you have to do to get model data into your CollectionViews and your UITableViews.
We think it's really a game changer.
It makes it incredibly simple and robust.
There's no more puzzling exceptions to debug and hard-to-write batch update code.
You can really just focus on what you want to do with your app and leave the rest to the framework.
DiffableDataSource is available for you to use today on iOS, TVoS, and MacOS.
In addition to providing to figuring out the diffs for you, and automatically committing them to your UI, you get these nice animated changes, right?
And there's no additional work for you to do to get that nice, pleasing user interaction effect.
The built-in diff is fast.
It has been heavily stress tested.
DiffableDataSource is a robust API that is ready for you to take and run with it.
So go out there and take it.
Adapt to your apps to use DiffableDataSource.
We can't wait to see the burden this is going to take off your shoulders and the delightful user experiences that you're going to be able to create with it with much less time, much fewer headaches.
If you found this talk interesting, and you work with CollectionViews, we've got another one that you're really going to love in Advances in Collection View Layout.
We describe an entirely new layout system, a way to just simply describe any custom layout that you want to have in your CollectionView.
And be able to see it implemented without any sub classing with highly performant results.
So we think you're really going to love this.
Make sure to catch that session.
And thank you so much for watching today.
[ Applause ]