Frameworks • iOS • 41:38
Get the most out of iOS's frameworks by understanding their core idioms and expectations. Learn about pervasive design patterns like "Model, View, Controller", Target/Action, Delegation and others used throughout Cocoa Touch. Gain practical knowledge of architectural idioms to organize your code for flexibility, clarity and reliability. See how to structure your app to take full advantage of Apple's frameworks.
Speaker: Bill Dudney
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
Welcome to Core iOS Application Architectural Patterns. My name is Bill Dudney and I work on UI kit. Thanks for being here.
[ Applause ]
So exciting! There's so much cool stuff this year. Okay so by way of introduction, what we're going to talk about today is sort of the list of common patterns that we find across all of the frameworks in iOS and Cocoa Touch. We are also going to spend some time talking about how we can apply these patterns to your code. How we can make our applications better by understanding these patterns, one, and then applying the concepts defined in those patterns to the code that we write.
All right, so common patterns in UI kit and beyond. The great thing about these common patterns is that once you understand it in one context, the way that we use them across all of Cocoa Touch ends up being very similar. So when you first look at the list of documentation, we have all these frameworks. The core video, core audio, core animation, foundation, core data, and so forth. It can be a little overwhelming. You look at that list and say holy cow, that's a giant fire hose that I have to ingest right now.
Well the great thing, though, is that, again, these patterns are used in a common way throughout this whole stack of frameworks. So when you understand one pattern in one framework, you can apply that knowledge across the whole stack of frameworks, so it really levels out the amount of learning that you have to do.
So let's get started with the first pattern. And that's target action. Now, target action is a consistent way to connect controls to custom logic. Things in your user interface connected to the code that makes your application what it is. So if you've ever done this in X code, you have seen target action in action. When you drag that connection from the button to your view controller, it's saying hey, whenever this button is activated, kickoff this message.
Here's an example from one of our applications. This is the message application. And we have several buttons on here. That top one, the cancel button. Of course when that's tapped, that's going to send a cancel message, it's going to delete the message and get rid of this Presented View controller. When the user taps on send, it's going to package up the message, push it off into the interwebs, it's going to end up in whoever's device you're having the conversation with.
Now, the really cool thing is these two classes that make these instances, the classes for these instances, are from completely different places in the class hierarchy. UI Bar Button item is a subclass of NS Object. UI Button is a subclass of UI Control. But both of them apply this target action paradigm so that you don't really have to think about the fact that they're different classes. They both apply this pattern in a very consistent way. So once you understand it for one, you understand it for both.
Getting a little more abstract, talking about target action, the idea is you have a controller, an initiator, something that kicks off the message send. That has a target object that it sends the message to, and then the particular message that it sends is called the action message. Once you understand this, again, wherever we use this, it's used in such a consistent way that if you understand it in one context, you understand it in all the contexts.
So with gesture recognizers, same thing. A gesture recognizer has a target and an action, and when you create that gesture recognizer you specify that, so again, it's exactly the same kind of concept as what we see with the control objects. So that's target action. A way to specify a connection between two objects, and get those control objects to invoke custom code in your application.
Next up is the responder chain. The responder chain lets your application handle events without knowledge of which particular object is going to be used to handle that event. Responder chain kicks off with some sort of initiator. It's just some object in your application that kicks off the process. It could be user action, it could be a control, or a controller.
That sends an action to the first responder. And that first responder might respond to it. And if it does, then the process is done. That responder has handled that message and it's complete. But if it doesn't, it's going to ask its next responder, hey, do you respond to this message? And if it does, then of course it will handle it. But if it doesn't, it keeps going up the responder chain until it finds some responder that actually implements the message, whatever that was that was asked to be done.
Change the color of a text field, or copy the contents of a text field, or whatever you happen to send up the responder chain. A particular place where we use this is with motion events. So when the user shakes the phone, we send this motion began, motion ended, and motion canceled, to the responder chain. And so that travels up the responder chain, looking for the object that responds to that message.
And the great thing is you can use the responder chain to respond to things like that, like motion ended, by implementing it wherever it makes sense in the responder chain. So here's a picture that diagrammatically expresses what all applications have, which is their responder chain. It starts off in the view hierarchy, it travels up the view hierarchy until it gets to a view controller.
The view controller, if it doesn't implement, it passes it up to the window, the window to the application, and then finally the application to the application delegate. And that's the responder chain. It lets you build a group of objects that are going to eventually find out who is responsible for implementing a particular message. Next up is Composite.
Composite lets us manipulate a group of objects as a single object, and we use this extensively in the view hierarchy. So I start off with a single view, created alloc init with frame, I create a new view, and then call add subview. Now, these two objects, the parent view in blue here, and the child view, the one in green, are in this composite relationship.
So now I can treat the whole hierarchy and I could have hundreds of views beneath that blue view, but I get to treat all of them as one Composite object, so I can do things like move that parent view. And when I do, all of its subviews move along with it. Same thing with other actions that I tell that view to do. Move it back and then I rotate it. All of the subviews in that Composite are treated together as one object.
We also use the view hierarchy, as I said earlier, in the responder chain. So as you add child views to a view, its superview, the new child view that was just added, its superview becomes that view's next responder, and so that's how it gets up to the point where it finds a view that has a view controller. It travels up its superview hierarchy.
And again, we use this same pattern in multiple places. If you've spent much time using the UI Dynamics, a dynamic behavior is able to have a child dynamic behavior, and if you group them together in a composite by calling Add Child Behavior, then those two behaviors are treated together as one thing by the physic simulation that's running in the background. So that's Composite, allowing us to build a group of objects into a tree that then we can treat as one single object.
Next up is delegation. That allows us to customize behavior in our applications without subclassing the object whose behavior we are customizing. So a great example that you see in almost every introduction to doing iOS development is talking about the application delegate. The application is delegating to the application delegate to customize the way it behaves. So there is a set of methods that the application delegate protocol specifies, application is going to go into the background.
Application is going to come into the foreground. Application wants to open this URL and so forth. All of those methods that you implement in your delegate customize the way UI Application acts, but you don't have to know anything about the details of UI Application. So this was a very particular decision made by UIKit to use this form of delegation.
We could have, on the other hand, we could have asked you to subclass UI Application, and that would've worked. There's actually GUI frameworks out there that ask you to subclass the objects that come out of the GUI framework. But if we asked you to subclass UI Application, you would have to understand a lot of detail about UI Application. Which methods are safe to override? Which methods are not safe to override.
If I do override this method, do I have to call super, or is that optional? We don't want to put that cognitive load on you. We don't want you to have to think about that stuff. So instead, using this idea of delegation allows us to delegate to your code to say what do you want custom? What do you want special to have happen when the application goes into the background or when it comes into the foreground? That allows you to customize the way UI Application works without us having to force you to understand all that detail about UI Application. Now, of course, there's still a lot of detail that you have to know, but that's what makes you awesome developers.
And again, we use this in many, many frameworks throughout iOS. Here's just a few. AV Foundation makes extensive use of delegates. Core animation has delegates on their layers. Game Kit has a couple of different classes, including GK session, that have delegates. And in every case, that delegate is about customizing the behavior of those other classes, without you having to subclass from them. Okay, so that's delegation. Next up is data source.
That allows you to customize data retrieval without having to subclass the object that needs the data. A common example from UIKit, UITableView has a UITableViewDataSource. That allows the table view to know how many sections it has. How many rows are in each section? What data belongs in each of the rows that are in that section? So the table view asks its data source all these questions.
That gives you the chance to customize how the data is retrieved, where it comes from, and so forth, and provide it to the table view. But you don't have to subclass table view. It would be the same situation subclassing table view and require you to know all sorts of detail about that, that we didn't want to have to force you to think through.
So here's some other examples inside the UIKit. Same basic story, UIPickerView says hey how many things belong in my picker view? PageView controller. Hey, how many things show up in my page view? Give me the next thing that's going to show up in here. So again, if you understand data source in one context, when you see that word data source, it means the same thing throughout all of the frameworks. So again, you get to leverage that learning across all different things. So back to the definition here of data source. Customize data retrieval without subclassing.
That looks an awful lot like customize behavior without subclassing. And in fact these two ideas are really similar. It's the idea of composing an object rather than extending a class, and so you use a couple of objects, compose them together to get custom behavior, rather than subclassing. It's a powerful idea that we use, like I said, in many different places. So that's data source. It allows you to customize where data comes from for an object that needs data without having to subclass that object.
All right, next up is Model View Controller. Model View Controller is in lots and lots of our documentation, lots of books have lots of things to say about model view controller. There's probably a definition for Model View Controller for every person in here. Or maybe more. The way I like to think about it is Model View Controller gives us a way to organize, to build an organizational structure around the responsibilities that our application has.
So let's start off talking about the model. So I like to think of the model, or the way I like to describe how to think about the model is it's the data, it's the stuff that makes up your application. And this is typically something that people don't have trouble getting their heads wrapped around. They typically can look at what their application is doing and say "Oh, I see, that's a model object." The view also ends up being a pretty well understood piece of the MVC puzzle.
It's the thing that displays the information to the user, and takes user interaction, taps and moves and so forth, and turns those into state changes on the model-- or sorry, and allows the user to make those kind of gestures, and the controller is then what's responsible for shuffling that back and forth, so it takes information from the model, and shuffles it so that it can show up in the view, and then takes the user's interaction in the view and shuffles that back into state updates on the model side.
So that's model view controller. An organizational structure for how we can think about the objects and their responsibilities in our applications. So now let's take these ideas, these set of patterns that we've discussed, and talk about how they apply in our applications. How they're used. To do that, we're going to sort of think through a sample application.
And when you build an application, I hope that all of you have downloaded the HIG, the Human Interface Guidelines. It's in iBooks as well. It's very near the Swift book. If you go look at the Swift book and then you tap on related books, you should see the HIG book in there. In there, there's a whole section where we spend time talking about something called an application definition statement. Many people who have unfocused applications aren't able to come up with this. Their architecture ends up being very disbursed or whatever, not well thought-through.
So I like to tell people to really spend some time thinking about this 30 second elevator pitch of what your application is and who it's for. So our application is going to allow people to share simple short updates about what's happening in their lives. So this simple phrase here has a bunch of architectural implications for what our application needs to look like. And some requirements that the application has.
So the application needs to share. So if I'm going to share some information with you and we happen to be in different places, we need to have a network, so I can take that information, push it out to the Internet, and then you're able to pull it down.
It's going to be short updates. That sort of implies there's going to be a lot, so we have a scrolling list of information. And then finally, what's happening, and since it's what's happening now, it needs to be fast. So we'll call our application Qwinkle, and here's a few ideas of things that this application needs to do, or we might want it to do. Mark items, edit post, add photos.
So in the process of sort of thinking through what our application does and then what the architecture of it is going to be, this is sort of the process I go through whenever I'm building an app. What's the application definition statement? From that, I get a list of features that I think might fit in that, and then, the all-important step of figuring out what to say no to.
So you have this long list, then you run it through the filter, of what is my application about? What's the first thing that I need to focus on? What's the most important thing that I want to focus on? And so that's going to allow us to drop these last three entries, and we're just going to focus on these first four things for how our application is going to work. So now let's go through and talk in a little more detail about the application, now that we have a sort of a picture of it. So here's a wire frame of the application. We have a list of updates from our friends.
They have avatars, and then we have this plus button on the top, where we can add a new entry. So the first thing to do is sort of spend some time conceptualizing what does our application look like through the lens of the Model View Controller design paradigm. And the first thing I like to do is talk about the model.
And a really good sort of first cut approach at how to come up with what does the model look like, I think, is to write down some user stories, or maybe if you've ever used CRC cards, like the titles of those, the list of things that you know that the application is going to have to do, and then pull the nouns out. So here we have a statement a user can specify an avatar, well that tells us user and avatar, these other statements-- a user can have an entry, a user can follow a friend, so there's relationships between these users.
All of these items, all the nouns out of this list tell us what the model objects are, and so from that, we come up with this simple model where we have Qwinkers have friends, they have avatars, and then they have a list of Qwinks, so that's the model.
Next up is the view. So how we're going to display this, since we're using UI Kit, we're on top of iOS, and we're going to make a beautiful application. We're going to use a table view. That table view is arranged in a composite. Of course you have the table view at the top. It has a list of table view cells. Those table view cells have a text view and an image view.
Since they are arranged in a composite, whenever this table view scrolls, and I move its bounds, I only have to move the table view. Aren't you glad you don't have to iterate through all the table view cells in your table view and move them by the appropriate amount? I do enough math without having to do that. All right, so that's the view.
Next up is the controller. Now, remember the controller's role in our application is to shuffle the data between the model and the view, and then take updates, and apply that back to the model. So our view controller is going to act as the data source for our table view.
So we have our UITableView. It has this dotted line or weak relationship to its view controller, and that view controller implements the data source protocol. So our view controller is going to tell the table view, here's the number of sections and here's the number of rows, based on interacting with the model.
Our view controller will also be activated, will be told to do things by the user interface, and will use target action, of course, to make that connection. So when you tap on the plus button, it says "what's my target?" Oh, my target is this object here, which happens to be the Qwinks view controller, what message should I send? Oh, that's the add a new entry message.
Our view controller is also responsible for dealing with the networking stuff. So the view controller has to manage the networking. It has to be able to get the data from the internet, turn it into objects that we can display on the screen. We'll talk in more detail about that shortly.
So that is the model view and controller for this initial scene in our application. We have our Public Timeline View Controller, which acts as the data source for the table view, and is able to get information from the model and shuffle that into the view by implementing that table view data source protocol method. The entry view-- sorry, the table view, is a composite. When we scroll it, everything moves together as one. The next piece that we had talked about was adding a new entry.
So when the user is adding a new entry, we're going to have a new view controller, and a new view, because we have a different way to present stuff. But the model class, the set of model classes, is going to be reused. We're going to use the same classes to represent that data.
So it's a different amount of shuffling, or a different kind of shuffling, right? Because we're shuffling data for a particular entry into the view, and of course the view, what the view does is different, so there's different stuff for the controller to do. The model class ends up being the same.
So we're able to reuse that. So the user taps on the button, that sends the action message to its target, which is the Public Timeline View Controller. We present the add a new entry, and then these cancel and done buttons would be connected via target action to the new entry view controller.
Now this view controller also has to interface with the network. When the user finishes entering this new entry and they tap the done button, we need to package up this information, push it out to the internet, it's going to live on some server somewhere. There's a potential smell going on there, because now we have two controllers that have to manage the same kind of stuff, and that's probably not good. We'll talk more about it shortly.
The last piece of taking our common set of patterns and looking at the way they work in this application, the UI Application is, of course, going to have an app delegate for application, and that's going to handle things like state restoration, it's going to handle background downloading, and the application becoming active and going into the background and so forth, that whole stack of stuff. So now we have this built, and change happens.
Our users come up with new things that they want to do. Our designers come to us and say hey, can you change this, and make this thing do this other thing? And so forth. So we have our initial table view controller, and our next set of requirements that we want to address is to be able to send private messages. So now we have our initial view controller, which has this networking code in it.
What are we going to do about this new view controller? It also needs to have networking code in it. This is really starting to be some pretty smelly code, right? We don't want to copy and paste this. If we took the understanding of how to download information from that initial view controller, we could, of course, copy and paste it into our new view controller, but then what happens over time if something changes, a URL changes, or who knows what else? The interface to that thing changes, and we have copy and pasted code. Now when we fix it in one place, it's not going to get updated in the others. So bad idea.
So instead, what we will do is let's re-factor this and pull that URL code out of the view controllers and put it into a download service. When we do that re-factoring, we'll start off with our Public Timeline View Controller, which probably had the networking code in it to start with. We'll pull all of that from the view controller into the download service.
So we'll call download on that object, and when it's done, it's going to send back a response. Now, I put a solid line in here specifically because I want to talk through the idea of making a hard relationship between these two things. The Public Timeline View Controller has to know the download service, because it's going to be responsible for creating it, but it's the download service, also has the hard relationship back to the Public Timeline View Controller, then we're going to have to add a property to that download service, and it's going to have to have a relationship to this Public Timeline View Controller. When we add a new view controller, do we add a new property? Like, we don't want to go down that path. So instead let's make that a weak relationship, and use delegation.
So notice how we took the idea of delegation that we have in UIKit, and now we're applying that to the code in our application. We're going to define a delegate protocol for this download service to call back to our code, so that we can customize what happens when that download service does things, like when it finishes downloading code, or downloading a JSON file and so forth.
Now, when we need to use this again, to interface with the private message view controller, it implements that download service delegate, and the download service doesn't have to know anything about either of those types other than the fact that they implement that protocol. All right, and with that, let's go take a look at the demo [beep sound].
All right, when I started writing the demo, I sort of went off the deep end, and I tried to build this whole application, I was building a service, and I realized this is just too much for us to cover in the last 15 minutes or so that we have left, so instead, what I've done is I've made a vastly simplified idea of this application. Let me run it for you, and I'll talk through what it does.
So I started with the master detail template out of X code, which creates for you a table view, and then that table view, when you tap on items in there, it navigates to a detailed view controller. I took out the detailed view controller just so we would have focus on this one view controller.
I added a refresh control, so that when you pull down, it kicks off the refresh, it downloads some information, and then it logs in the background to let us know that it was successful in downloading that information. So in a real application, of course, you're going to be processing the JSON that comes back from that download, and doing all sorts of fancy stuff. But for our demo, we're going to be simple, and just have this download happen. So let's look at the way the code works over here, briefly. So here's our view controller, and notice it's implementing all these delegate protocols for the NS URL stuff.
We want to move that from our view controller, over to this new object, the download service that we're going to implement. We don't want our table view controller to understand all of this detail about how networking works. We want to capture that in one place so we can reuse it.
So we're going to go through all this code here that does all that, and just move it. Now, I'm not going to make you sit and watch me type all this stuff in and copy and paste it in, and have me mess it up and have it not work. So instead, I have a bunch of get tags in here [tapping sounds].
Awesome! Okay, so our view controller now no longer has this knowledge about the URL session and how it works. We removed all those, and moved them over to our download service. We also took all that code from in here that was interfacing with the network, and moved that over to our download service.
And that's the URL that we're using to download and so forth. So our view controller did have all this knowledge, and we've moved it over into our download service. Now the download service has all that information. Our download service, though, doesn't have any API on it yet. So we haven't made it so that we can actually interface with it. Our view controller still has to manage making the network call. Right? We don't want the download service just going rogue and downloading stuff whenever it feels like it.
The view controller, being in control of how things happen with the user, taking the user's input from the screen and turning that into stuff that happens on the back end, we want the view controller to be in charge of making that happen. So we need to add a couple of methods to our download service to allow it to first do the connection, create the NS URL session, all the configuration stuff that goes along with that, and then we also want a method to start that, to kick it off.
So we'll do another magic switcho-change-o [assumed spelling], and our download service now has these two methods connect and start. In the connect method, we're creating NS URL session configuration stuff, and then in the start method, we create the download task and tell that to start. From our master view controller, we are still kicking off the download from our refresh control, and of course, that refresh control uses target action in order to make the connection between the control and our custom code. But we've greatly simplified what's happening in our view controller. Instead of it having any knowledge about this networking, it only has knowledge about kicking it off. About starting it. About making it happen. All right, so now we can run the application again.
Now when we pull down, notice here it's telling us in the log that it finished, but our refresh item hasn't stopped. So the issue, what's going on, is our downloader now has all the knowledge about what's going on with the download, but no one is informing the controller that that happened, that it's finished. And so it finishes just fine, but our refresh isn't able to stop, because our view controller doesn't know that the download is finished.
If only we knew a way to customize the behavior of a reusable class. Of course, we do, right? We use delegation to make that happen. So now, what we need to do is create a delegate protocol. We need to create a property on our downloader to have a delegate property and make that delegate property implement that protocol.
[ Background Sounds ]
So our download service has this method spelled out in the delegate that says, hey, the download service did finish downloading this URL, which is the original URL that was asked for, it downloaded it to a particular URL, and then that last R unit is an error, saying everything is fine if we pass nil there, or hey, something went wrong, and this is what happened. Now, if we were going to build a more sophisticated networking stack here, what we'd want to do is provide many more delegation points. Many more places for customization.
Things like, hey, I need some authentication information. Other things like that. Or, I have finished downloading 10 bytes out of the 100 bytes that I'm going to download, so you could update the spinner appropriately from the view controller. But we'll just leave it at this for right now.
And then in our download service, really important piece of the puzzle is when the URL session finishes downloading, when the, you know, we kick off the download, and when it finishes, it's going to call back the NS URL download task is going to call back and say hey, I'm finished, we need to make sure and tell our delegate that that happened. That callback goes from our download session to the view controller, which is then going to use that knowledge that the download has finished to end refreshing.
We also put the responsibility of what to do with that document into the view controller. So if you notice up here, further up, we're doing some file moving around, taking the file and moving it and so forth, in a real application this is another place where I would create another service.
Probably something like a JSON parser, and I would take that JSON parser and say, hey, here's the stuff that just got downloaded, would you please take care of parsing it and turning it into real objects, create another delegate protocol for the JSON parser, and the JSON parser would call me back and say, hey, everything has finished parsing. And then that's when I would actually have all the information I need to change the way the user interface looks.
One other thing that I always like to talk through with developers is we have stepped over the boundary where we understand and can start thinking about how UIKit and Foundation are implemented. So let's take a step back and take a look at our download service. So the download service specifies that it implements this set of protocols.
Every one of those protocols' names ends in delegate. The responsibility of anything that is a delegate is to customize the behavior of the object for which it is the delegate, right? So what we are doing is specifying that we are able to fulfill that role. Now, in the code, I don't want to have to go show it to you because there's so much code there, but in the code, I am making this download service the delegate of the download task NS URL session. So imagine with me, if you will, what the code looks like in NS URL session or in NS download task.
There's got to be a chunk of code in there somewhere that does something very similar to this. I hope that helps you think through, like how does the puzzle fit together? How does all this work? We do the same things that I'm asking you to do. We define a protocol that says here's the customization path, here's the customization points that you have for this reusable object. If you implement this method, I will call it, at a particular time.
Now you're building that for your own application. You're defining this download service delegate protocol. And it has a single method in it right now that says, hey, I finished downloading this stuff to this particular URL. That would be a perfect place for us, as I said earlier, to take that URL, pass it off to a JSON parser, let the JSON parser crunch on it for a little while, perhaps put the data through core data into a database, message us back and say yes, all that information has been parsed and all that information is sitting in your database, and you're ready to update your table view. Okay, so one last piece of stuff in the demo here that I'm not particularly happy with. Well, let me go ahead and run it again just for fun, so we can see it stopping.
So now we're back to the functionality that we had before we started hacking and slashing and doing surgery on our application. All right. So one last, like I said, one last piece of stuff in here that I'm not particularly happy with. If we go up here in the download service, and I glanced over this earlier, because I didn't want to confuse where we were in the talk.
If we go in the download service, we see this thing where we're calling out to UI application, shared application, give me your delegate. And then we're casting that to a class that we know about, and we had to import that header file. So now we've made our download service dependent on our application delegate.
That is, that's smelly code. You don't want to do that. Because your app delegate, its responsibility is not providing information to download services. The application delegate is supposed to be about maintaining the relationship between your application and the rest of the OS. The methods on there are things like I'm about to go in the background, I came back from the background.
That's the stuff that you should focus on for your application delegate. I see this pattern repeated in many people's code, and it's a really bad idea. If you're casting shared application, application delegate to your class, and then you're sending it messages, that's a bad idea. So let's do a little bit more clean up to fix that up.
[ Background Sounds ]
So a couple things that we did. The first part is adding an additional call-back into our delegate protocol. So we added a new method, download service did finish with identifier. So that's going to let the download services delegate know that this download has finished that's related to this particular identifier. And we're going to call that in the same place where we were calling out to the UI Application delegate.
Then, in our view controller, in the implementation of that additional delegate method, we're going to invoke that background session completion handler. And not to go too far into the details around there, because it's not germane to talking about delegation, but that's just a call-back that we tell the OS. Hey, we're finished processing this download and you can take a new snapshot of the application. I have a link to the talk on NS URL session from last year, and there was also one this year that has a lot more detail about how all that works.
Now we have gotten rid of the dependence from our download service to our application delegate. So we no longer have this sort of marrying of these ideas or placing too much responsibility on the application delegate. The application delegate is responsible for one thing. Keep it focused on that one thing, and don't start spreading it out throughout your whole application and using it for many different things that do not include its set of responsibilities.
So in summary, there are lots of frameworks in iOS. But the cool thing is that once you understand these common patterns that are used throughout all these frameworks, delegation, data source, target action, responder chain. Once you understand that in one place, you understand across the whole stack of frameworks. So you get to leverage your learning, understanding these patterns gives you power to use all of these frameworks in your application.
And the next thing I want to say, in terms of summary, is just go for it. There-- I have talked to many other developers who spend time like fretting about, "is this thing a controller? Is this thing a model? Is this thing a view?" Just go for it. Build stuff. Go out there and make amazing stuff you can always re-factor it like we did here, not quite live on stage, but pretty close. You can always do that. So just make stuff and you will learn so much from doing that.
All right, so for more information, you can talk to my buddy, Jake. There's also the DEV [assumed spelling] forums. Here's a link to those related sessions I was telling you about. The talk this afternoon on Advanced iOS Application Architecture and Patterns has tons of fantastic stuff in it, and it's got a little bit of information about Swift and some of the new patterns that we're going to see emerge because of the new language, which I'm really excited about. And then here are two other talks from previous years that have more information. Thank you very much for being here, and I hope you have a great rest of your WWDC [applause].
[ Silence ]