More Videos

  • Introducing Combine

    Combine is a unified declarative framework for processing values over time. Learn how it can simplify asynchronous code like networking, key value observing, notifications and callbacks.

    Resources

    Related Videos

    WWDC19

  • Hello. Thank you.My name is Tony Parker, I'm themanager of the Foundation Teamat Apple, and today I'm excitedto have the privilege tointroduce you to our newestframework.It's called Combine.

    Let's talk about asynchronousprograming.

    Here I have an app that I'mworking on that allows studentsto sign up for my brand-newwizarding school.And as you can see, it's gotsome pretty simple requirements.

    First, we need to have a validusername which we're going tocheck by making a networkrequest to our server.We also need of course to havematching passwords which issomething that we can checklocally in the app.

    And while we do all of thesethings, of course we need tomaintain a responsive userinterface, not blocking the mainthread.So let's try using it and seehow this works.

    First, I'm going to start typingthe username like, I don't know.How about Merlin.Seems like a pretty good wizardname. And already there's plenty ofasynchronous behaviors going on.I used Target/Action to listenfor notifications about the usertyping.I use a timer to wait for theuser to stop typing just for alittle bit so I don't overwhelmmy server with network requests.And finally, I use somethinglike KVO to listen for progressupdates about that asynchronousoperation.Now if we continue, we'll findout that we get a response fromthat request and we have toupdate our UI.So I pick a new username andthen my super-secret securepassword of 12345.Please don't use that password,just for demo purposes.

    But here now we've done a lotmore asynchronous work.We had to wait for the responsefor our URL session request.We had to merge that result withthe result of our synchronouschecking and finally I had to doall of those things -- once allof those things were completed,I had to update my UI againusing something like KVC.

    So across the Cocoa SDK you'llfind plenty of asynchronousinterfaces.Some you saw here likeTarget/Action, but there's a lotmore, includingNotificationCenter and lots ofad-hoc callbacks.These are API's that take aclosure or completion block.All of these things haveimportant and different usecases.But sometimes when you need tocompose them together, it can bea little bit challenging.

    So with Combine we set out notto replace all of these butinstead to find what was commonamongst them.

    And that's what Combine is, aunified declarative API forprocessing values over time.Combine is written in and forSwift.That means we can take advantageof Swift features like Generics.Generics let us reduce theamount of boilerplate code thatyou need to write.It also means that we can writegeneric algorithms aboutasynchronous behaviors once andhave them apply to all kinds ofdifferent asynchronous interfaces.

    Combine is also type safe,allowing us to catch errors atcompile time instead of atruntime. Our main design point aboutCombine is that it iscomposition first.What that means is that the coreconcepts are simple and easy tounderstand, but when you putthem together, you can makesomething that's more than thesum of its parts.

    And finally, Combine isrequest-driven, allowing you theopportunity to more carefullymanage the memory usage andperformance of your app.

    So let's talk about those keyconcepts. There's only three: Publishers,Subscribers and Operators.And we'll go over each in turn.First, Publishers.

    Publishers are the declarativepart of Combine's API.

    They describe how values anderrors are produced.They're not necessarily thething that actually producesthem.

    That means as descriptions, theyare value types which in Swiftmeans we use a struct.

    Publishers also allow forregistration of a Subscriber;that will be something thatreceives these values over time.

    Here's the protocol.It's called Publisher.It has two associated types: theOutput, that's the kind of valuethat it produces, and theFailure which is the kind oferrors that it produces.If it's not possible for aPublisher to produce an error,then you can use the type neverfor that associated type.

    Publisher has one key function.It's called Subscribe.As you can tell from the genericconstraints on this function,Subscribe requires theSubscriber's Input to match thePublisher's Output, and theSubscriber's Failure to matchthe Publisher's Failure.Here's an example of aPublisher.This is our new Publisher forNotificationCenter.

    As you can see, it's a structand its Output type isNotifications and its Failuretype is Never.

    It is initialized with threethings, which center, which nameand which object.If you're familiar with ourexisting NotificationCenter API,they should look very familiar.So again, we're not replacingNotificationCenter.We're just adapting it.Next, Subscribers.

    Subscribers are the counterpartto Publishers.They are what receive values,including the completion if thePublisher is finite.

    Because Subscribers usually actand mutate state upon receipt ofvalues, we use reference typesin Swift which means they areclasses.

    Here's the protocol forSubscriber.As you can see, it has the sametwo associated types: Input andFailure. Again, if the Subscriber isunable to receive Failures, thenyou can use the type Never.

    And three key functions.It can receive a subscription.A subscription is how aSubscriber controls the flow ofdata from a Publisher to aSubscriber.

    It can also of course receiveInput. And finally, if the Publisherthat it is connected to isfinite, then it can receive aCompletion which can be eitherFinished or Failure.Here is an example Subscriber.

    This one is called Assign.

    Assign is a class and it'sinitialized with an instance ofa class, an instance of anobject and a type safe key pathinto that object.

    What it does is when it receivesinput, it writes it out to thatproperty on that object.Because in Swift there's no wayto handle an error when you'rejust writing a property value,we set the failure type ofAssign to Never.Let's talk about how these fittogether.

    So you may have some kind ofcontroller object or other typewhich holds your Subscriber, andthat will be responsible forcalling Subscribe with theSubscriber to the Publisher,attaching it.

    At that point, the Publisherwill send a subscription to theSubscriber which the Subscriberwill use to make a request fromthe Publisher for a certainnumber of values or unlimited.At that point, the Publisher isfree to send that number ofvalues or less to theSubscriber.And again, if the Publisher isfinite, then it will eventuallysend a Completion or an Error.

    So again, one subscription, zeroor more values and a singleCompletion.So returning to our example,let's say that I have a modelobject called Wizard and all Icare about today is what gradethat wizard is in.Let's start with Merlin who iscurrently in fifth grade.What I want to do is listen fora notification about my studentsgraduating, and once they'vegraduated, I want to update mymodel object's value.

    So I start with aNotificationCenter Publisher onthe default center aboutgraduation of Merlin.

    Next, I create an AssignSubscriber and tell it to writethe new grade to Merlin's gradeproperty.

    Next, I can use Subscribe toattach them.However, as you might expect,this doesn't compile.And the reason is because thetypes don't match.

    NotificationCenter makesnotifications but Assign beingconfigured to write to aninteger property expects aninteger.

    So what we need is something inthe middle to convert betweennotifications and integers.What that is, is an Operator.

    Operators are Publishers untilthey adopt the Publisherprotocol.And they are also declarativeand therefore value types.What they do is describe abehavior for changing values,adding values, removing valuesor any number of different kindsof behaviors.

    And subscribe to anotherPublisher which we call theupstream, and send the result toa Subscriber, which we call thedownstream. Here is an example of anOperator.This one is one that you'llbecome very familiar with whenyou use Combine.It's called Map.

    Map is a struct that isinitialized with which upstreamit connects to and how toconvert its upstream's outputinto its own output.

    Because Map doesn't generateFailures of its own, it simplymirrors its upstream's Failuretype and it will just pass itthrough.

    So with Map we have the tool weneed to convert betweennotifications and integers.

    Let's see how.

    So keeping the same Publisherand Subscriber and before, I'madding this converter which asyou can see is configured toconnect to thegraduationPublisher and has aclosure.That closure receives anotification and looks for auser info key called NewGrade.

    If it's there, and if it's aninteger, then we return it fromthis closure.If it's not there, or if it'snot an integer, the new use adefault value of zero.What that means is that nomatter what, the result of thisclosure is an integer andtherefore I can connect this tothe Subscriber.And everything connects,compiles and works.

    Now all of this constructionsyntax can get a bit verbose, sowe also have a more fluentsyntax.Here's how it works.As an extension on the Publisherprotocol, meaning it's availableto all Publishers, we added aseries of functions named aftereach Operator.Here is the one for Map.

    As you can see, its argumentsare everything needed toinitialize a Map except forupstream.And the reason is because as anextension on Publisher we cansimply use self.

    Now this may seem like a prettytrivial convenience, butactually this is what's reallygoing to transform how you thinkabout asynchronous programing inyour app.

    Let's return to our example butuse the new syntax.So here I am starting with theNotificationCenter Publisher forgraduated on Merlin.

    Once I receive a notification, Ithen map it using the sameclosure as we saw earlier, andthen I assign it to the gradeproperty on Merlin.

    And you can see this syntaxprovides a very linear,easy-to-understand flow of whathappens step-by-step.

    Assign returns something calleda cancelable.Cancelation is also built intocombine.Cancelation allows you to teardown the sequence of Publishersand Subscribers early if youneed to.

    So this step-by-step syntax isreally the heart of how you useCombine.Each step describes the next setof instructions in a chain.Transforming values as they maketheir way from the firstPublisher through a series ofOperators and ending in aSubscriber.And we have a lot of theseOperators.We call them our DeclarativeOperator API.

    They include functionaltransformations like Map.We also have Filter and Reduce,list operations like taking thefirst, second or fifth elementof the Publisher.

    Error handling like turning anerror into a default orplacement value.Thread or Q Movement, forexample moving heavy processingwork to a background thread orUI work to the main thread.And scheduling and time,including integration with fromloop, dispatch queue, supportfor timer, timeouts and more.

    And with so many of theseoperators available, it can beperhaps a bit overwhelming tothink about how you willnavigate amongst these.So what I encourage you to do isreturn to our core designprinciple about Combine, andthat is composition.

    Instead of providing a fewoperators that do a lot, weprovide a lot of operators thatjust do a little bit each,making them easier tounderstand.

    So to help you navigate amongstall these operators, we drewinspiration for their names fromthe existing Swift CollectionAPIs.

    Here's how.

    Let's imagine a quadrant graph.So on one side I havesynchronous APIs and the otherasynchronous.On the top I have single valuesand on the bottom I have manyvalues.

    So in Swift, if you need torepresent an integersynchronously, you might usesomething like int.If you need to represent manyintegers synchronously, youwould use something like anarray of integers.

    In Combine we took theseconcepts and mapped them intothe asynchronous world.So if you need to represent asingle value asynchronously, itcomes later, we have a future.If you need to represent manyvalues asynchronously, that's aPublisher.

    So what that means is that ifyou're looking for a particularkind of operation that youalready know how to do with anarray, try using that name on aPublisher.

    Let me show you an example.

    So here I chose to use a defaultvalue of zero if the key was notpresent or if it was not aninteger.Maybe instead it would be abetter idea to not allow thisbad value to progress and end upwritten into my model object.

    So one thing I could do is allowthis closure to return nil andthen filter out the nil values.

    Well, in Swift 4.1, the standardlibrary introduced a name forthat operation.It's called compactMap.And so Publisher has one too.And it behaves in a very similarway.If you return nil from thisclosure, then compactMap willfilter it out, keeping it fromprogressing further down thestream.

    Let's build up our step-by-stepinstructions using a few morefamiliar names.

    Let's say that only students infifth grade or higher areallowed in my school.I can do that using Filter.Filter takes a predicate andonly allows elements that passthat predicate to proceed.This is exactly the samebehavior as Filter on Array.

    Let's say furthermore thatyou're only allowed to graduatea maximum of three times.

    So on Array, if you need to takethe first three elements, youcould use prefix 3.On a Publisher, if you want toreceive the first three elementsonly, you can use prefix of 3.What it does is after itreceives three values, it willCancel the upstream and send aCompletion to the downstream.So stepping back, let's see whatwe have here.We have a NotificationCenterPublisher that listens forgraduations on Merlin.

    Once he graduates, we will fetchthe NewGrade out of thatproperty, out of thatNotification.And then we will make sure thatthe value is greater than fifthgrade and that it has onlyhappened a maximum of threetimes before finally assigningit to the grade property onMerlin.

    Now Map and Filter are greatAPIs but they're primarily forsynchronous behaviors.Combine really starts to shinewhen you're working inasynchronous world.

    So here are two more operatorsthat I'm going to talk aboutthat can be really useful forthat.First, Zip.

    So let's say in my app beforethe user is allowed to continue,they need to wait for their wandto be created which is threelong-running asynchronousoperations like this.

    So the Continue button becomesenabled once all three thingsare finished.This is a job for Zip.

    Zip converts several upstreaminputs into a single tuple.

    Because it requires input fromall of its upstreams in order toproceed, it makes it a kind ofwhen/and operation as in, whenthis and this and this havefinished, do this other thing.

    So for example, my firstPublisher produces A, and thenwhen my second Publisherproduces a 1, I now have enoughinformation to create a tupleand send that value downstreamto my Subscriber.

    In my app, I use the version ofZip that takes three upstreamsto await the result of threeasynchronous operations thateach give me a Boolean result.So I map the tuple into a singleBoolean and here I've written itinto the isEnabled property onthe button to turn it on.

    So after you're done waiting foryour wand to be created, likeeverybody else, my students haveto agree to a set of terms andconditions before they areallowed to proceed to playingwith their wands.What that means is that allthree of these switches have tobe enabled before the Playbutton is enabled.However, if one of them is thenlater disabled, we need todisable the button.This is a job for CombineLatest.

    Like Zip, it converts severalupstream inputs into a singlevalue.However, unlike Zip, it requiresan input from any of itsupstreams to proceed, making ita kind of when/or operation.

    In order to support that, itstores the last value that it'sreceived from each upstream.And it's also configured with aclosure that lets you convertthat into a single downstreamvalue.

    So for example, when my firstPublisher produces A, and mysecond Publisher produces A1, Ithen run my closure whichstringifies this and sends itdownstream.Later, when the second Publisherproduces a new value, I cancombine it with the value frompreviously from the firstPublisher and send that newvalue down.

    That means that I get new eventsas any upstream changes.

    So in my example app, I used aversion of CombineLatest whichtakes three upstreams, theBoolean states of all three ofthose switches as they change,convert them into a singleBoolean value again and writethat to the isEnabled propertyon my Play button.

    That means that if any of themare false, the result is false.But if all of them are true,then the result is true, thusenabling the button.So we designed Combine to beadoptable incrementally in yourapp.You don't have to convert everything over to use this.So to get started, I have a fewsuggestions on places that youmight find in your app todaythat you can use Combine for.For example, if you useNotificationCenter, you receivenotifications and then you lookinside them to decide whether toact or not, try using Filter.If you weight the result ofseveral asynchronous operations,then you can use Zip, includingnetwork operations.

    And finally, if you use URLSession to receive some data andthen you convert that data intoyour own objects using JSONDecoder, we have an operatorthat will help with that aswell. It's called Decode.

    So we went over the basicstoday. Publishers, Subscribers andOperators.However, there's a lot more toCombine.And that includes error handlingand cancelation, schedulers andtime, and some great designpatterns including using Combinein different modules or betweendifferent areas of your app.And of course integration withSwiftUI. For more on that, please watchCombine In Practice.

    That's all I have today. Thank you so much for your time.[ Applause ]