Beyond the block based API: Building a simple future
Thomas Visser speaks about using Futures in Swift
I'd like to talk about futures. The first thing that I did when Swift came out 2 years ago, is create my own BrightFutures- my own futures library. It seemed like the right thing to do at that time. I was using BF tasks in Objective-C and everything was in ID. It seemed like Swift would offer the right language tools to make really great futures. Today I would like to share some of that enthusiasm that I have about futures with you, if that's okay.
To do that, I'm not going to show you how to use BrightFutures. I'm also not immediately going to explain to you what a future is, I'd like us to stumble upon it, like rediscover the concept by refactoring a little app that I made. This little app here with some birds. The puffin is my personal favorite, although I like Swift as well. Here there's a simple view controller, table view controller. It will load a view and when the view appears, it will go to the data source, get the birds, it's asynchronous and then have a call back whenever the birds are loaded from the internet, reload the table view, simple as that.
Let's look into the data source. Here is the getBirds method. Short and sweet. This will actually go to the internet and download a JSON file with some information on birds. Then it will parse the data into an array of dictionaries. Every dictionary will become a bird, a model struct and then for every bird we will do an additional request to get the image.
What we see here right away is that it's a little bit pyramid shaped and that's something that you'll see when you use completion handlers for asynchronous API design. I think, it's my opinion that completion handlers are not a great tool for asynchronous API design, so you will see this pyramid shape. It's also the way we use completion handlers mostly, that Apple uses completion handlers. It's not easy to do error handling in a nice way and it's also hard to compose those handlers together.
What I would like to do is instead of giving this method a completion handler, I'd like to give it a return type and give it like some kind of place holder for the result of the asynchronous operation. Let's call it an Async. It will not compile for now, because it doesn't exist yet. We're going to create it in a bit. We need to return and I think, so we will put all this inside the scope. This scope is not going to do threading or anything, it's just a scope where you create the Async and only within that scope you can complete the Async, so you can put the resulting value in.
To do that, we're going to have this scope have a closure as a perimeter and you'll call that closure, when you have the results and put the results of this operation into the Async. If you go to the call site, in the view controller what we can do now instead of passing the completion handler, we'll get the Async, we can actually save it in a variable if you want. Then whenever you want later on, register for a completion callback like this, or even register for a second completion callback if you want. For now one is enough. This looks decent and already like a nice improvement.
Now let's make it compile. I have this empty file called Async, where we will create the Async type that we are going to use, but of course in Swift you should start with a protocol, we'll see why in a little bit. The protocol will have a type alias, I'm going to call it Res, which is the type that the asynchronous operation will complete with eventually. We are going to need a way to get that type and we're going to need the initializer, which is quite important, so there's no threading going on, that's why I'm having no escape. That's called scope, so it's this closure that takes a closure which takes the resulting value as a perimeter. You'll see, I hope it is clear. Then of course we also need the onComplete method where we can register the callback, which we'll get the result value and return Self, which is nice for composibility.
Now let's implement this protocol with the Async type. We're going to need a generic type place holder to be able to define what this Res type is. Now I'm going to cheat a little bit. Here I have prepared the initializer so we get the scope, we're going to run it and we're going to pass it to the completion handler, so whenever the user of this Async, the creator of this Async type, this Async instance, calls the completion handler, we will get the resulting value and put it in the Async and then we'll call all the callbacks.
The second part is actually registering the completion callbacks, so for the purpose of this talk, I'm just going to assume that we always want to have a callback on the main thread. If the result is already in when you register, you just immediately call the completion block, the callback that we want to register and otherwise we'll append it to the callback array and then whenever the result comes in we will call it.
Let's see if this works. onComplete like this, I can just make it do this. This works, right, let me see. Good, still the birds. Now that this is working, I'd like to talk about error handling. In the talk yesterday about error handling, we learned about the new error handling features in Swift 2, but there was also a question from the audience about how do we use that in the asynchronous situation. The answer was: it's not really possible.
Let's look at the alternatives. How can we do asynchronous error handling and let's look at what Apple is doing. For example, there is the dataTaskWithURL method. This has a completion handler with an optional data, optional response and optional error. In the documentation, it says that either the error will be non-nil or the data and the response will be non-nil, but there's no guarantee from the type system that this will always be the case. Do we have to check, what do we have to do when everything is nil, or none of these values are nil?
I'd like to not live in this uncertainty and get rid of this as soon as possible, so what you could do is get rid of that and as soon as possible try to put it into something that represents a success or a failure. For that you could use Result, probably you all know it, I'm using the one from Rob Rix. I'm doing the check for data and error in this place, but then the rest of my code, the remainder of my code I can just use Result. I'm returning here in this function, that I'm adding to NSURLSession. I'm returning an asynchronous result, which means that I'm defining an asynchronous operation that will eventually return with the success with the data and then the response tuple or with the BirdsError.
Before I start typing Async all over the place, I'm going to create an alias, more or less, in here. I'm going to call it a future, because I think that in some definitions this actually is a future. In some functional languages, this, the Async will be called a future, but in a lot of libraries for Objective-C and Swift, the type that I just created is often used as a future, so it has two generic perimeters, the value and the error and it just forwards that to the Async type having a result with that value and that perimeter.
Now we have this ResultType and we can build upon that and add logic that helps us deal with the success and the failure case, with separate code-paths. For that, I could add code to the future type, but what's nicer of course is to create an extension on AsyncType, but only if the result in the Async type is actually a Result. There what we could do is add an onSuccess callback register method. So onComplete will always get called, but onSuccess, will only be called if there's actually a successful result. We'll use onComplete under the hood and the same we can do for failure- onFailure. These will help you only deal with the success case and the failure case.
If we go to the data source and update our getBirds method and make it return a future here and here, and now it also should define that it will have a BirdsError, if something goes wrong. Now we need to wrap this into the Success enum. We will get to failure a little bit later. We need to go to the call site in the view controller and here where Birds is now is actually a Result, because we now have an async with Result. This is the code that we want to do if it's a success, so we're now going to use onSuccess. The onSuccess callback call is defined to actually get the success value from inside the result. We can also add the onFailure block and do something responsibly with the error. Like this. This still works.
I think the call site here looks pretty nice already, but if you go to the data source, there's still some work to be done, so for the last part of this talk, I'm going to paste in a little bit of code, and then we are going to refactor this getBirds method. What I'm adding to the AsyncType, whenever there is a result, so to the future actually, I'm going to add map and flatMap. What map can do is you pass to the transform closure, that will execute whenever the future is a success. Then you can transform the success value into a new value and it will return the future with that new value.
flatMap almost works the same way, except that you can return a Result or a Future from the transform method. flatMap will make sure that it flattens out that additional layer of result or future.
The last bit which is an extension on SequenceType, which allows me to work with a sequence of future. We'll see why in a bit. Back to the data source, we can comment out all this. What we can do is start by going to the session and fetch the URL and I'm going to call map and map will get the data and the response, because map is only called when it's actually a successful fetch from the internet. This data, I'm going to parse into JSON, so I have this method birdsJsonFromResponseData and it's down here. I'm going to replace it with the result method, so this will return a result that will have an array of dictionaries if the parsing is successful or it will return an error.
If we continue, because now I should have JSON, let's see. Almost- I have a result with JSON in it. So I can use flatMap instead. Now we should have the JSON that I need. What I'm going to do now is map over that array of dictionaries, so this is the built in map on the built in array from the standard library and I'm going to parse every dictionary inside that array into a bird. For now, this method returns an optional bird, so only if the parsing succeeded, you'll get a bird. To make sure that we don't get optionals in the array, I'm just going to use flatMap for now.
Now, we should have an array of birds, let me see. An array of birds, what we now need to do, the last step, this is where futures really shine I think, is we need to do an additional request for every bird to get it's image. I have this getImage method here, I'm going to replace it of course with a method that given a bird will return a future with the bird, UIImage tuple. In here, I'm going to map over all the birds and call getImage for every bird and return this. That should give us an array of futures, maybe, let's see.
The result now is a future with an array of futures with bird image tuples. Almost there. So this is why I pasted in this extension to SequenceType, because I can sequence this. What sequence actually does is it works on a sequence when there are futures in that sequence and it will turn a sequence of futures into a future with an array with all the success values. So that's one future that we can wait on. Now we have a future with a future, with an array of bird images. What we need to do now is flatMap, that will give us a future with an array of bird image tuples. That sounds familiar, because that's exactly what we want to return here. If I return this, it seems to compile. I remove all this we are left with something that looks way better if you ask me. And the best thing- it still works.
What we've seen is that using a hundred lines of code, creating a really small async type, it allows us to refactor this nasty looking code into something that looks really natural to me. Actually, it doesn’t really look like asynchronous code, I think that's really nice, so async or a future will give you a place holder for the result of an asynchronous operation and you can work with it as if it's not a asynchronous. I think this is really nice, really easy to understand. That's basically what I wanted to show. There is one thing left. There is this “done” button that I want to press, so thanks very much. If you like this, I encourage you to check out BrightFutures, or any other futures library or write your own, or do all of that. I'm @thomvis88 on Twitter, I work at Highstreet. Thank you very much.