Frameworks • iOS, OS X • 48:00
Explore a selection of high-level software engineering techniques presented in the context of Cocoa Touch. Learn how to manage complexity in large codebases by clearly defining where truth resides, by controlling state with Swift's powerful value types and immutability, and by thinking in terms of composition.
Speakers: Colin Barrett, Andy Matuschak
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
Thank you. Thank you very much. My name is Andy Matuschak. I work on the UIKit Framework. And later I'm going to be joined by my colleague, Colin Barrett, who also works on UIKit. And today we're going to talk about Advanced iOS Architectural Patterns, which is going to sort of be a continuation of Bill Dudney's talk from this morning, the Core iOS Application Architecture and Patterns. We're going to be taking some of those ideas a little further and just in case you didn't see that talk this morning or even if you did, I want to tell a little story that explains I think how we all got here.
So maybe you start off by yourself and the project starts small, your well intentioned, you're keeping everything orderly, you're adding features and everything is still good. You add more features and they kind of pile up and you got momentum behind you now. You have all these users and they're demanding.
You had some more developers because maybe that will help, but it doesn't seem to have quite the impact on your progress that you'd been hoping for and you're making changes faster and faster and bugs seem to be appearing faster and faster. And it seems like all you're doing is spending your time fighting bugs.
And you think, OK, well I'll knock a few down and a few more come back. You're not adding new features. You had some unit tests to try to get a handle on what's going on here. Somebody's told you that's going to help things, but it seems to help initially and then the bugs start coming back. And then you learn actually your unit test can have bugs too.
[ Laughter ]
What do we do? People say: Software Architecture. And you go, "All right, cool. Software Architecture. All right." What's Software Architecture? You search around the Internet, you get a whole bunch of blogs. They're listing like, "Oh man. Never do this stuff it's going to make your architecture bad." And it seems like there's a lot of contradicting opinions. Some people say start small and some people want to design everything up front. Seems like these discussions just keep coming back to taste, maybe dogma. All you really know maybe is that your taste is improving a lot faster than your ability.
[ Laughter ]
You can tell that something is starting to get smelly. You can tell you've got a problem, but maybe you don't know what to do about it. So we're going to try to help. We can only scratched the surface today. The idea is to take some time honored concepts from computer science and software engineering and to present them, hopefully, less abstractly and in a way that might make sense to you as an iOS developer with your apps. Today, we're hoping to provide insight, not dogma.
[ Applause ]
We're not--
[ Applause ]
I'm so glad. I'm so glad to hear that. Epistemology is important. You need to know why you're having architectural problems. Not just a bag of tricks that you can use when faced with any particular issue. That way, hopefully you'll have a real objective measure that you can use to compare approaches. That way, when your colleagues disagree with you, you can actually talk about what might be better and what might be worse and why rather than just citing a bunch of catch phrases. All right.
So today, we're going to talk about 3 broad approaches that you can use to consider the complexity of your application and evaluate approaches to dealing with it. First, when we have tangled meshes of information networks like this, we're going to clean them up by actually designing the information flow in our application rather than just letting it grow ad hoc like a weed. And then when our objects start to become amorphous blobs, we're going to separate them actually define what their responsibilities are and hopefully get each of those circles a little smaller.
And finally when the very ground seems to be shifting out from under you, we're going to take advantage of immutability and in particular, some new features of Swift to really get a handle on things. So those are the big concepts from today. And we'll start by talking about designing information flow.
Now, much of the complexity in a modern application is really just shuffling information at events around. You have delegate handlers, and target action callbacks, you have completion blocks, and you have some signals. And how should those information and events go through your application? You start wiring stuff up. You set these object as a delegate of this other object over here. You say add target action, and every time you're doing that, you're adding a little node to this network.
And that network is getting gnarlier and gnarlier and maybe you've drawn networks like this for say, object ownership in your application. Which object owns which other object or maybe for a workflow. But in this section, we're going to talk about thinking conceptually about this diagram as it pertains to information flow in your application.
Where is information coming from and where is it going? And the first question I'll ask is where is truth in my application? And in order to do that, and in order to keep things hopefully somewhat concrete, we're going to turn to a demo application which is having its own problems with information flow. All right.
So over here, we have an application and it's going to be written in Swift. And that's just because I like Swift quite a lot. But the topics we're discussing in this particular section really don't require Swift. All right. So with the journaling application, write down what's going on in your life, keep track of things. Maybe you're Abraham Lincoln. And like any good journaling app, there's a UI to add an entry. You've gotten a bug report from a user talking about a bug with the photo edition UI in your application. So we'll go ahead and start making a journal entry.
We will add a photo, taken this one from apple.com and there's this feature that lets me show and hide the photos that I've attached to my journal entry. Everything is looking good so far. But this bug report I got said that the app seems to break when I'm playing with this button. So, I'll play with the button. Something really glitchy is happening there and we have a crash.
That's no good. Now we could look at this back trace but probably the problem has already happened. And we can see the issue is-- now this exclamation point here is going awry. Rather than trying to address this specific crasher, I'd like to draw your attention to the infrastructural problem which is underlying this crasher.
So let's take a look at how exactly this expanding and collapsing of the PhotoDrawer feature is working. Here's expandPhotoDrawer. And first we say, OK do we have a PhotoDrawerView? If we don't, make one, get it all laid out so it'll expand from zero height. Animate it to full height and, you know, update that button, a little bit of bookkeeping. Of course then, in collapsePhotoDrawer, if we already have a PhotoDrawer which we want to collapse, we animate it shut. And when the animation is done, we remove it.
It seems pretty straightforward. You know, you might say, well this option, this option seems to be the problem. Like you're letting users interact with your UI while the animation is going on. You shouldn't do that. And, OK, so you could remove this option. But you know, we do actually want to keep things fluid. There's no reason that the world should have to stop just because this PhotoDrawer is opening up.
And furthermore, we're starting to get a little sense of that taste versus ability tradeoff we were talking about earlier. I mean, hopefully you're looking at this and you're saying that "Well, I don't exactly see anything wrong. So trying to work around this bug by just disabling the button doesn't seem like the best thing to do.
What's really going on?" And even if we were to disable this button, things get somewhat worse. If I rerun this application, I'll show you there's sort of a side feature. So say that it was the case that you could not interact with the Show Photos button while the animation is going on. Well, down here is our body and as we start typing into it, we hide that PhotoDrawer because we want to have more space for the keyboard.
So it's actually not good enough just to disable this button to make this problem go away. The problem will still exist because we collapsed this PhotoDrawer when we focused the body. If I were to keep going on like that, it would crash. So there's something more fundamental at play here. There's some mold growing in the corner and I'd like to return to a diagrammatic approach to examine that mold. Now this is boxes and arrows representation of the information flow on our application.
We've got this photos drawer tap action and we've also got a text view focus action, and they're both doing something based on a piece of information. And that piece of information is: does that photos drawer exist, does that drawer view exist already? And we saw in the code, if it doesn't exist, OK we'll make it. And if does exist, we'll start an animation. When that animation is done, we'll throw it out. That's fundamentally what's going on here.
This bug is occurring because we're getting our information from the wrong place. There's another piece of state, which is, is the drawer semantically expanded? And that's separate from, does the drawer view exist. Because the draw view isn't torn down until the animation completes. If we start collapsing the drawer, then we want to think about the state of the system as the drawer is collapsed now, just like if you start animating a view to the other side of the screen, its model value is already at its destination.
The drawer view's existence is more like the presentation value of the view. It's like a proxy for that underlying truth. Should the drawer view be expanded, you can module any animations that are going on. So we can deal with this problem by reifying that piece of state. We can make a property. Say, you know, is the drawer view expanded right now, updated immediately when we start collapsing that drawer view and when we start expanding that drawer view.
And then once we do that, we can look at this diagram and say, "Wait, wait, wait. Why are these actions getting their truth from whether or not the drawer view exists?" What really matters is whether or not the drawer is supposed to be expanded or not. And when you tap in the text view, you should only really be collapsing the PhotoDrawer if it is actually expanded.
You don't want to try to collapse the PhotoDrawer if it's in the process of being collapsed already but that drawer view hasn't been removed yet. Now in this talk, I'm not actually going to make the code changes that we're talking about here because we have a lot of material to get through and I want to cover it quickly.
But here we've been able to solve this problem, at a conceptual level at least, by thinking about our system in terms of how information is moving through it: where are we getting data, how we're making decisions. And considering first: where is truth? Who really knows that state which one place should be consulted for it? And that will help us get all the boxes that we need in our diagram.
Now, once we've gotten all the boxes we need in our diagram, I want to draw your attention to the difference between that root level truth, that one place in your application that actually knows where things are supposed to be and the values which are merely derived from it. So in order to do that, again, let's return to our demo application.
And you'll see here that as I continue typing, there's a character counter. And that character counter is there because you might be an extremely outgoing and extroverted person with your journal and you might want to take advantage of this feature that we have which automatically shares all of your journal entries to Twitter. So we gave you a character count in order to help you make sure that it's actually going to fit when you post to Twitter. Simple enough. It goes up as we delete, and it goes down as we add new characters. And that's all fine.
But if we think about this photo's feature that we have, you know, when we post to Twitter, we're going to have to put a URL for that photo in the body of the tweet. So that needs to be included in the character count. And we got a bug from users saying that they tried to make a post and Twitter rejected it even though the UI said it was OK.
So, I added this photo, you could see it's added to this entry, and we can see that the number of remaining characters is still 140. And if I start typing, watch I'll enter one key, and we jump down to 118. It's like it immediately reconsidered the URL. I'm sure you've all seen this kind of bug quite a lot. Something is stale. Something that was supposed to happen has not happened, and unfortunately, that kind of problem is really difficult to debug. There's nowhere to break.
So, we can look at the code and say, "OK, what's supposed to happen?" There is an updateCharacterCount method here and it's very simple. It just updates this character count label with the result of our Twitter entry encoder. So there's 2 possible problems here. Possible problem number 1 is: the Twitter entry encoder is at fault. It's returning a wrong value. Possible problem number 2 is: this method is simply not being called when we add a photo.
So, we can look to invalidate one of these hypotheses. We can look to sort of guide to where we should be addressing our debugging abilities. We can do that by just adding a break point here and I will go ahead and create a new journal entry here to set this up again. I'll add a photo. And we see that updateCharacterCount is never called.
But when I enter a key here, it is called. So our problem appears to be that this method is just not even getting called when it should be called, and let's continue to follow this rabbit hole upwards, I guess, and see who calls updateCharacterCount. Now it's only called in one place and it's called on "textViewDidChange".
Now kind of an esoteric factoid about the UIKit and at AppKit APIs is that if you programatically set a textViews value, the textView does not emit delegate callbacks saying that the textViews value changed. And then the same thing is true for basically anything else that has a delegate callback.
If you programatically set the value that is corresponding to that callback, UIKit/AppKit will not call you back. So, you could react to this by saying, oh geez, got to work around this weird UIKit behavior. I guess we'll add an extra call to updateCharacterCount when we add a photo which happens down here, somewhere, right here.
You know, we could-- we can call updateCharacterCount there but, you know, what if we added an audio recording feature. You know, take a voice note about how your day is going. What if we added a video recording feature? All of these things are going to have to remember to update the character count, and, I hope anyway that you're starting to get that sort of taste versus ability smell again. Something seems amiss here, and it's not exactly clear what it is. So again, I propose that the solution is thinking about the information flow of your application diagrammatically.
Here again is a diagram of the information flow in the application. There's a text view and there's a character count. Both of these things are getting their values from the model. So that seems OK. We've got truth, the model is holding the truth and we're using the right inputs in our information flow, but we're still not getting the right behavior. The reason for that is that derived values have to be treated differently.
Let's think about what these derived values are in comparison to the truth which provides their inputs. Now, if you have a derived value, it does a couple of things. It computes some other value like a character count from some set of inputs. That's straightforward; we saw that in updateCharacterCount. Separately, it needs to recompute that output when one of the inputs changes.
These requirements are really very much like a cache. And I propose to you that everywhere in your program where you have something like a character count, which seems like a very straightforward thing, it's just a value derived from another value. All of those things have many of the same properties of a cache, which means that they're subject to the same problems that a cache has as well.
For instance, when your original data changes, you need to make sure to update the cash data or else you are going to have staleness bugs. And on the flip side, if the original data hasn't changed, then you shouldn't be going about generating new cache representations of that information either, or else you're going to have efficiency problems.
Now our problem here is staleness, and you can see that the same issues apply for truth versus derived values as they do for caches and the values which are being cached. So let's focus on the staleness issue here and try to understand what's going on with our application. How do we get here? Where did we go wrong? And how do we model it with these information diagrams? So, returning to this diagram, we see that the model's value defines the text view value.
I guess I didn't show it to you, but take me on my word on that, you know, the model's value changes and we set the text view values. It's very straight forward. We haven't seen any bugs with that, at least not yet anyway. So let's go ahead and assume that this is how it works , nd when the model is changed, the text view is invalidated. So, all of that is fine. The problem that we're having is with the character count. We've seen the definition of updateCharacterCount. We know that the character count is deriving its value from the model.
But when the model changes, the character count is not invalidated. Rather it's when the text view changes, the character count is being invalidated. And the lopsidedness of this diagram should hopefully indicate something to you. You know, each of these arrows is basically doing half a job. You're splitting up these responsibilities in 2 different places, and that's really the cause of our bug. We can solve our bug by merging those responsibilities back to one place.
We can solve the staleness problem by saying, "OK whenever the model changes, we need to make sure to update all the values which are derived from that model." It's really easy to do this in Swift because we've got these fancy new property observers. All we have to do is add that updateCharacterCount call to our didSet block. And in general, you're going to be way better off if you always keep those arrows merged together; unless there's a very pressing reason not to.
So, that's just one more tool in your tool bag for thinking about the complexity in your application. Actually draw out these information flows. What are the boxes? What are the arrows? Do each of those arrows actually mean both definition and invalidation? Because they need to. Or else you might have staleness problems.
Now, finally, I'd like to turn your attention to the text view. Now we said, OK, the text view is getting its value from the model, and that part appears to work. But we know that when the user types into the text view, that model must be getting updated.
After all, I can make a new journal entry and it appears to get saved. So there must be some reverse relationship here, too. How do we think about that schematically in the same way? And in particular, this looks like an infinite loop, but it's not. We don't have a stack overflow. So these arrows must be asymmetric in some way.
And I propose to you that in thinking about how new truth is created, you need to carefully consider the asymmetry of those relationships in your application. All right. So let's return to our app and talk about creating new truth. Now our application has another feature I haven't told you about. That feature is that I can share my journal with a loved one. I can let my fiancée see all these entries I write.
And that's a great feature, but it's complicated somewhat by the fact that there's this Edit button here. You know, if I were to start editing this entry, and you see our character count issue on display again, if I were to start editing this entry-- oh, you don't need this break point anymore.
Then, we don't expect for my fiancée if she's staring at her phone to actually see this edited journal entry, right. Because I could hit Cancel, and I'd be throwing it out, presumably the old entry would still be there. So, I said that we're working with the model here, but we must not be working with the model. We must be working with some other model that has a relationship to the first model. Because if there were really one model, then there will be no real way to implement this cancel button and she would be seeing those changes as soon as I made them.
I'm going to suggest that thinking about the relationship between this editing UIs model and the underlying model, which it is editing, is actually the same as the relationship between the model underlying the text view and the new values that the text view is emitting to the model. But if we're going to think about this effectively, we really need to be able to think about it from a diagrammatic perspective so that we can see where everything is and how it's going.
So, let's talk about these arrows. Let's talk about what they mean. What's that asymmetry? We know if we start with just a text view, that the text views value is defined by the models value. We also know that if we edit the text view, somehow that model value is getting updated.
But, the text view is deferent to the model value. If the model value were to change out from out of the text view, somebody were to just set the body to something else, that text view would probably just update immediately to that new value and if some other piece of the program were wondering what is the body value of this journal entry, they wouldn't ask the text view, or at least we hope that they wouldn't as we saw earlier. Instead, we hope, that they would ask the model.
The model is truth, and these derived values are deferent to truth. These new values they emit, as you edit, are like suggestions flowing back up the graph to the model along these dashed arrows. Truth: new pieces of information. Fact: these things flow from the model to the derived values and suggested new pieces of information flow back from the text view to the model; because of course there could be validation. The behavior of our application with respect to that 140-character Twitter limit could be that we just truncate. So, it could be that that suggested value is not applied exactly.
In this way, this relationship is asymmetrical, and we can use the same structure of thinking to think about the "entry viewing controller", which presented that editing controller, and the "entry editing controller". There's some original model, and that's what my fiancée is seeing if she looks at the app.
And that's the one that's going to still be there if we hit Cancel and then there's the model that we're editing which began from a copy of the original model. So in the same way as these other relationships in our graph, the model that we're editing is derived from the original model.
And when we hit that Done button, our editing controller is suggesting a new value for the model to the viewing controller. That is how new truth is created. I want you to imagine with me, because it isn't on the slide, that there is a dashed arrow coming from the model back up to the original model; that is how you can think about this relationship. Just as the model may validate the text views value as it's coming out of the text view, the original entry view controller may be validating the edited entry.
For instance, if she were able to edit those journal entries also, there could be a conflict now. And so when the new model value is suggested, some UI might be presented to allow that merge to happen. In the same way, the editing controller, its value is derived from the viewing controller and the new value is only a suggestion to the viewing controller.
So, we've worked through how these techniques can help us solve several real world application problems and hopefully they suggest to you ways that you can solve the kinds of problems you face in your app every day. You just have to actually think about how information and events are flowing through your application. First, where is truth? Who really knows? Who is really responsible for those fundamental pieces of state, information and events in your application? And then once you start thinking about how they flow through your application, remember the difference between truth and the values derived from truth.
Remember that values derived from truth are in many ways like a cache and needed to be treated as such. And finally, remember that when new truth is created, that relationship is asymmetrical by necessity in order to avoid some kind of infinite loop. One direction -- facts flow. And then the other direction -- suggested new values flow, which may need to be merged or validated in some way. And by thinking systematically about the information flow in your application, you can really get a handle on complexity. All right. So, now let's move on. I'm going to invite Colin Barrett up to talk about defining clear responsibilities for your application.
[ Applause ]
Thank you, Andy. Good afternoon, everybody. In this next section, I'm going to show you how to identify and tease apart responsibilities in your application. Let's start with an example: form validation. We've all at least used a form like this. Many of us have, maybe, implemented one. You know, it's not as simple as just putting some text fields on the screen and a button.
There are rules about what values are allowed in the different text fields, the four we have here, and how the other interface elements in this view are enabled and disabled, or shown and hidden, depending on the values of these text fields. So, let's say we're implementing this view here.
Where would we start? Well, we'll need to know when the user finishes editing a text field. So, we'll implement the text field "did and editing" delegate message on our view controller. And in that delegate message we're going to need to consider each text field and if they're all valid, enable the sign-up button.
But, we can't exactly consider all of these fields uniformly. They all have different rules. Let's look at the rules for the username field. We have this regular expression that our server engineers gave to us and we have to match that against the contents of the username text field. If we don't have any-- If it doesn't match, we're going to need to show the users somehow that they've messed up and then you go back and correct the mistake. Now, there's one other complication here and that's if our text fields are empty.
If our text field is empty when we're checking each field, we're going to want to leave it alone. But when we're considering the state of the sign-up button, we're going to want to leave the sign-up button disabled. So, we're going to want to leave it disabled because we don't want the user to proceed with a partially filled out form.
So, let's go back to this username part and see what that would look like in code. So, yes, this is a lot of code, but it's all different sorts of things. It's intermingled. We have local variables to track the state of the text fields and of empty text fields. We have a bunch of regular expression code right next to that, and in between all of that, we have these 2 lines, which are really the most important for what the view controller does, which is to manage and configure its constituent views.
So, much like we can diagram information flow in our application, we can also diagram responsibilities. Who does what? Here is the diagram that we've been thinking about so far. We have our view controller, which manages and configures its constituent views, and we have our views, which display the data they're given with core animation and core graphics. But we've identified a third overlapping responsibility: validation.
This may not seem so bad, but if we add another view controller that also has to do similar validation, they don't have that same logic in two places. A bug in one has to be fixed in the other. And since these are in different places, it's likely that they'll diverge over time.
A type of rule get added in one place but not the other because it's not necessary there, making it even more difficult to change and fix these common issues over time. So, what we want to do is separate that responsibility out into one place that can be shared by both view controllers.
So, to do that, we first need to identify what validation actually is. When thinking about these types of questions, it's useful to think of the inputs and outputs. Or, to put in another way, what information do I need and what questions am I trying to answer? So, as I build through this common sense explanation of what validation is, I'm going to fill in the inputs and outputs in this table.
So, validation seems to be that if you give me an input value, I can tell you if it's valid. And if it's not valid, I can tell you why. These pretty directly translate to fairly simple data types. And if we look at what that looks like here in Swift -- forgive the syntax error there -- we have our output types right here but we're missing our input.
That's because we want to leave this actually open to interpretation in this case. Because a good technique for dealing with complicated responsibilities is to be able to build larger units out of smaller ones. So, in this case, we'll build larger validators out of smaller ones. So for those larger validators, the input will be implicit in the constituent validator's input. This technique is called composition. We're composing a larger validator out of smaller ones and it works totally fine in Objective-C, too. I also really like Swift.
So, let's look at what a username object, username validator object, that implements this validation protocol would look like. Well, we have our input here, it's a String, and this validateWitherror function is where we're going to have our regular expression code. It's now isolated. It's not intermingled with all of these other code. We can do likewise for password validation.
But, maybe wondering that this only represents the validity of a single password field, then that's correct. We need to represent that two password fields match in value, in addition to not being too short, not being high MoM, things like that. So, we'll create a SetPasswordValidator that has two password validators. This is composition in action. It's going to represent, in addition so that the password fields individually are valid, that they're also matching in value. We can also apply this composition technique to our overall form. We have our usernameValidator, setPasswordValidator and emailAddressValidator.
This is also where we'll handle the behavior of nil, we talked about earlier. The constituent validators will allow nil, because we don't want the individual text fields to show any sort of error state when they're empty. That would be just confusing. But, this SignUpValidator will check that its constituent validators have non-nil input. And if any of those inputs are nil, it'll say it's not valid, allowing us to easily disable sign-up button. So, let's return to that workflow we were looking at earlier and see what that looks like.
Now, we're able to consider each text field uniformly because all we do is set the input on the corresponding validator. We're also able to decorate all of the text fields uniformly as well. The logic and that responsibility has been moved elsewhere. We're only talking to an abstract interface.
So, let's define clear responsibilities. We've separated out the responsibility of validation from the rest of our program. We've used composition to build up larger pieces from smaller ones. Let's move on now to our third section, simplifying with immutability. You may have heard on the internet that mutability is "bad", and you should feel bad for using it.
But nobody's really told you why. Why is it bad? I mean, it's easy to use, you just set it, right. We're working in an imperative oriented languages here. We have statements. So, to illustrate that, let's look at another diagram. In this diagram, the red box represents immutable object, which has the value of five, and these circles here represent other objects that are passing this mutable data around. So, when A passes this mutable object to B, B probably doesn't want that object changing out from under it.
So, how do we ensure that that happens? Well, A still has a reference to this object. So, B depends on-oh, sorry, if-- yes, exactly, if A changes it, then B sees that change. So, B depends on A behaving in a specific way. They're linked in this sort of responsibility and dependency graph. Now, likewise if this object now gets passed to C, these other objects could still have references to it. They could still change it. So, C may depend on A and B.
But it actually gets a little bit worse than that, because A or B could be waiting for C to make some sort of change. Maybe A has registered for KVO observation. Maybe B is the delegate of this object and is waiting for some sort of callback to happen. The specific timing of that could be part of B's implementation. If C changes when it calls some setter, that could screw up B.
So, we're seeing here how mutability ties all of these things together. It's one of the biggest reasons why you feel that drag that Andy was talking about as your application grows bigger. And it also explains why adding abstraction often doesn't help. As long as you're passing around the same mutable data, it doesn't matter how many layers you have. You're in fact just lashing more mutable layers onto this big ball. So, let's look at what would happen if this object was immutable instead.
We couldn't change its value. When A passes it to B, well, A still has a reference to it, but it can't change it. No one can change it. It can never change. If B does need to make a change, it has to make a copy and pass that copy to C. There are no arrows here; there's no dependencies. Everything has the data that they have and that's it.
So, there is a tradeoff here though, right? Mutability is easier to think about locally. Think about building up a string or an array. It's also wasteful to create intermediate copies when no else is mutating our-- when no one else can see those mutations yet. You may be wondering is there a better way? Happy to say this, Swift Structs are that better way.
They have opt-in mutability via the mutating keyword. This lets you choose whether or not a struct is mutable or immutable, based on using the let or our keyword to introduce the binding to the name when you declare it. So, Swift Structs also have the property that they are called by value which just means that a new copy is automatically created from you when you pass that struct from one function to another. You don't pass a reference or a pointer like you do with an Objective-C or Swift object.
So, let's go back to that diagram and see what that would look like one more time. Here we have our Swift Struct, happy green box, and when we pass it from A to B, an implicit copy is made and that's what's passed to B. B can happily mutate. A won't see those changes and again when we pass it, another copy is made, and C gets its own copy.
So, that's one sort of immutability, and that's how Swift Structs can help us be-- get a lot of the benefits of immutability without sacrificing programming, ease of programability. But there's another kind of immutability I want to talk about. We can see that in UIMotionEffect. You're not familiar with UIMotionEffect? It simply adjusts properties on views based on gyroscope data. We use this to achieve the parallax effects on the Home screen and elsewhere throughout the OS. MotionEffects are reusable across many views. If you have one type of motion, you only have to create one effect.
MotionEffects also have very low latency requirements. We don't want to get behind the gyro data because that looks laggy and it really ruins the magical effect of parallax on the Home screen and other places. So, again, we're going to show how this is also immutability. Once again by considering the inputs and outputs of the MotionEffect process. MotionEffects take a device pose, which is simply a description of the device's orientation in space. In return, a relative offset for each key path that we want to change.
It may be somewhat surprising to describe a motion effect as immutable. It doesn't really have a bunch of properties not unless we're dealing UIInterpolatingMotionEffect. It feels more like a function. But immutability is actually a deep idea, and has deep power. Because motion effects take an absolute device pose and return relative offsets, they can always return the same dictionary for the same input device pose.
In much the same way that if you don't have a setter, you'll always return the same value for your getter. So, to really show that this design is immutable, let's look at some alternative designs for motion effects. Let's say that we thought that having delta inputs rather then an actual absolute device pose was a better design.
Well, we would still have our device pose data, but it will be deltas and we'd still be giving back relative offsets. But to calculate those offsets, we would actually need to figure out what the current orientation of the device was. So, we would depend upon all previous poses as an implicit form of input.
Now our inputs are not just all-possible device poses but all possible sequences of device pose deltas. That's a lot bigger input space. It's harder to test, it's harder to reason about and it's also not immutable because as our application changes over time, we're going to get different answers.
Likewise, if we had absolute offsets rather than relative offsets, we would have a value for each key path that we're returning in absolute offset, we would be dependent on a particular view. And that particular view would make this MotionEffect not something that was reusable across many different places, and again would make it so that its value for a particular device pose would change over time.
So, that's simplifying with immutability. We've seen two different forms of immutability, and how we can leverage those to build simpler, more easy to reason about, portions of our application. Being able to reason abstractly about pieces of your application is critical. If you have to know every nook and cranny of the system and wonder if changing one thing is going to cause something else to break, your app just becomes this big spider web, that you can never really escape from.
We've talked about a lot today. So, let's just review everything we've gone over right now. First, we talked about how to design information flow in your application. We learned the difference between truth and derived value, and how new truth is created. We saw how to define clear responsibilities, how to tease apart different portions of your app and how to isolate them. We also saw how to use that isolation to build things with composition.
We also saw how to simplify with immutability to increase our ability to abstractly reason about our application. So, what now? You've heard this talk; maybe, hopefully, you're all fired up about these concepts. Go back to your app; put some of these in action. When you're designing a feature, think about the information flow, think about the responsibilities. Share this talk with your coworkers.
Tell them about a time in your app, specifically your app, where there was a bug, where you were maybe updating some sort of cache, that you didn't really even realize was a cache until just now. That's all we've got today. For more information, contact Jake Behrens. Bill gave a talk this morning about some other types of design patterns in our frameworks. Thank you for listening. Have a great afternoon.
[ Applause ]