Essentials • 53:14
It's often a small quantity of code that uniquely solves a problem or turns a good application into a great one. Learn how the amazing features introduced in Leopard can improve your Cocoa application. Hear some of the latest tips and tricks directly from Cocoa framework engineers.
Speaker: Ken Ferry
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript was generated using Whisper, it may have transcription errors.
So here, because it's so simple, at least to begin with, there's not much in it. It has the draw rect, which does the drawing, and then it also has an accessor pair set up for storing and getting the color, which it wants to put up. So we're going to talk about both of these, at least briefly. I'm going to start with the drawing, even though really the accessors are where I'm going to spend more time, believe it or not. So let's look at this first. So is there anything interesting about this? Well, yeah, a little bit. So for drawing, what are we doing? First, we're setting a color into the context. The way graphics works on Mac OS X is that there's a state machine, which is called the graphics context.
You set attributes like the fill color or the stroke style or things like that, kinds of joins, and then you draw, and the drawing is always through a suspect to the current state of the machine. So the first line is setting a color into the machine, and then the second line is using it to fill a rectangle. And what's interesting, maybe, the first thing, is that, yes, it's a rect fill. That part makes sense, but what's this compositing operation, composite source over?
Well, let's talk about that briefly. So compositing operations are ways of combining drawing. So we talk about there being a destination and a source whenever we're doing new drawing. And what that refers to is that the source is whatever's new, the new pixels, and the destination is what was already there.
And compositing operations tell you about how to combine them, what formula to use. So the normal mode is called composite source over, and it's just exactly what you expect. It just looks like draw it and it just looks completely normal. And that's going to be the main theme for this little tip is just normal is composite source over. That's what it means. Some of the more abnormal modes like this, this is the kind of thing you see if you use something else. This is what you see if you see destination atop. Fairly obvious that while you can come up with cases, certainly, and interesting operations where you can make good use of these extra modes, for the most part, that's not what you want. for the most part, you always want to be using composite source over. So if you ever see a compositing operation argument and you don't know what to pass, you almost surely want to pass source over. Okay, so take it to heart. And one of the reasons I want to bring this up is because a somewhat attractive method that you might want to use instead of NSRectFill using operation is NSRectFill. It looks simpler, right? Well, it is, I suppose, but it very likely doesn't do what you want because it actually is the same as NSRectFill using operation, but it passes copy mode, and copy mode is not source over, it's not normal. What does it do? Well, copy mode completely replaces what was already in the context, in the destination, with the new source pixels, including the opacity information. So what you see is something like this. Wherever your new drawing is transparent, the context actually becomes transparent rather than just showing through what was there.
So very likely, you often don't want to be using NSRectFill. It's certainly fine if that is actually what you intend to do, but for the most part, you want to be using NSRectFill using operation with the source over operation. Okay, so that means that this is a source of bugs in some applications, and let's see what this looks like if you've made this mistake. So what would happen if we set the clear color into the context? This is the color which is fully transparent, and then we do a RectFill with it. Well, since that's replacing the drawing that's already there, it's going to make whatever you're drawing into transparent. So what happens if we draw this into just this color view in the window? You'd expect the window to become transparent, right? Well, not quite. This is what you'd actually see in Snow Leopard. So what's going on here?
Well, the issue is... Oops, I'm sorry. The issue is that the window is by default marked as opaque. Okay? And what opacity means is it's usually just a bit of metadata that you're advising some graphic system. Oh, by the way, this thing doesn't have any transparency. So as an optimization, you don't have to draw whatever goes behind it. And that's what the Windows server is doing here.
It thinks this window is opaque. So it doesn't bother to actually fix whatever is in the frame buffer behind it. And you just see this junk. If you're not careful to actually do something about it. Yeah, so right, so that's what I want to mention. So if you see that kind of drawing corruption, very likely what that means is you have something that claims to be opaque, and it isn't true. For whatever reason, it has some transparent pixels. So there are two common ways this can come up. One with views can be marked as opaque or transparent by overriding isOpaque. And by default, views are not opaque, but if you implement it and say, yes, that's an optimization, by default, windows are considered to be opaque by the Windows server. So you have to say if yours isn't. Which you would do like this. You'd say window set opaque no. And then if you were to draw, then you'd see something more like what you're expecting. You'd be able to see through the window with that same code to what's behind it, which happens to be the app kit Snow Leopard release notes, which you should all read. Okay. So use window set opaque to control window transparency, if that's what you want. Okay. So that's my drawing interlude. That's really all I have. Most of what I'm going to have to say about drawing today. So if you already knew all that, then don't worry. There's more coming. So now let's actually go ahead and talk about these accessors. In particular, what I'd like to say about those accessors is it's sort of annoying to write them.
We'd really like to, and in Leopard, we added a feature to allow you to not have to write these accessors so often, which is Objective-C 2.0 synthesized accessors. You can write something that looks like this sometimes. However, and this is what we'd really like to do because it's just busy work to have to write the accessor. However, it doesn't seem immediately appropriate in this case because of this line down here. We need to do something because whenever the color changes, we need to invalidate our own display so that it will redraw and we actually see the new color. And there's no scope in the implementation of Objective-C 2.0 accessors for doing that. That if you actually want to do something custom, you need to override the method. So, I mean, that's not bad, I guess, but it's a little bit annoying, especially in these Vue classes. So what can we do? Well, we can use key value observing.
So let's just a quick rundown of what key value observing is for people who may not have used it too much. So it's a way of getting property change notifications. So when you hook up a controller layer, say, and you say, I'd like to observe the color property of some model object, you can receive a notification every time it changes, like here. And then when you receive this notification, you can just do whatever you want. So for example, you might update some view. And then after that happens, control returns back to the caller. It's all synchronous.
That's something that some people don't always realize. It's not like this happens later. The, all of the changes by observers are made before the method even returns. Okay, but now we wanna actually use this to fix our problem. So what is it that we wanna do? Well, we're gonna keep a list of those keys, which when they, those properties, which when they change, we want to update our display.
which we're going to call keypads affecting display because that's kind of what they are. And then we're going to observe those keypads and whenever one of them changes, we'll invalidate the display. So this is going to allow us to use the synthesized accessors even in these cases, well, basically for all the cases where it's simple, but the only thing we want to do is invalidate display. So let's see how this ends up working out.
So the method where you receive those notifications that I was mentioning whenever a key value pair changes is observed value for key path. And what you want to do, often the implementation of this method is not quite right the way people look at it. In particular, you see how the first line here, what I'm doing is I'm checking if the context is one I recognize. If the context is color view needs display context, then I'm going ahead and invalidating the display. Otherwise, I'm calling super. This is important, and this is something that people don't always get right, that the KVO context is a non-optional argument. It's the only way you can distinguish your observation from observations that somebody else is doing, which is unusual in Cocoa. Usually a context is optional, and it's usually just something you pass in and you receive back later that you can do whatever you want with it. But here, the issue is that if NSObject, say, was observing some particular key path, and it happened to be the same key path you were observing, you need to not block those observations. You need to make sure that all of your superclasses are also getting a hold of the notifications they expect to be receiving, or else you might break the system. And the way that you need to do that is this.
This is the way to do it, is you check the context argument. So I suggest you pass that in. Okay. So something that makes this a little bit easier is that Xcode these days, I think just in 3.1, If you type the beginning of the word observe out in the gutter right where you would want to implement the observe value for keypath method, well, it'll actually complete it, and it'll give you the right form, and then if you fill it in, then you'll be good. So that's nice. And.
I'm glad you like that one. Okay. So, all right. So going on, so that's what our observed value for key path method looks like, what we do when we notify. We also need to actually set up the observances. That's what this would look like. Let's just go straight down the page and look at it. First is just a little aside a little bit. So what is this #pragma_mark line? This is a way of just organizing your source code and Xcode. What it does is that in the function pop-up, you'll see a bold separator, which is nice. It really helps when you've just got it, especially in large classes where you've got a lot of methods. Okay, but moving on. Okay, so now we have this context argument that I was talking about before. We just made it a static NSString global. There's just a single one of it, and it has a unique name. It has something, well, unique in that it's unlikely anybody else in the system would try to use the same context for anything, because we called it, we prefixed it with our class name, so we wrote it color view needs display context. Seems reasonable.
Okay, then when it comes time to actually set up start observing, this is what we would do. We would call self at observer key path with the property and the context as the needs display context. And you see here I'm calling a method to return the list of properties which I want to invalidate display, which I've called more or less key paths for values affecting display. However, I gave it a prefix. Why did I give it a prefix? Well, it's too generic. The problem is that this is a sufficiently useful method I think that, for example, AppKit might actually end up supplying it for you someday, possibly. And it would be bad if you defined a method that did something like this and we defined a method that did something like this, but they actually had slightly different semantics. Or if we wanted you to call super or something like that, it could end up breaking your application. So the suggestion is when you're implementing something that's really generic, that's not specific to your application, that you try to do some prefixing or something to keep it out of our namespace. Or to keep us from... Excuse me, I shouldn't call it our namespace. to keep it from conflicting. So a good example of this was that when the setHidden method was added, I don't remember if it's either 10.3 or 10.4, that really caused a lot of trouble because it turned out a lot of people had tried to define setHidden on its view, which did something, wasn't quite the same thing as ours. So there was some hacking actually necessary to make sure that we didn't break people.
Okay, I don't want to get too caught up on that. So that's where we do our setup. We also need to do teardown, of course. That's pretty straightforward. you just remove the observer, which is self in this case. Okay, that's great. However, we haven't actually called them anywhere. Those were just convenience methods I set up. Where might we want to call them? Well, we can call them in init. That's a possibility. So because this is a view class, I'm overriding init with frame, and I'm overriding init with coder to do the setup so that whenever my properties change for the entire lifetime of the object, the setNeedsDisplay call will happen. So why particularly these two methods? Why did I do it in init with coder and init with frame? Why didn't I override init, for example? Well, these are the designated initializers. And this is something that people sort of know about but don't seem to quite have the right idea on. So with designated initializers, first of all, a class has not one designated initializer, but any number of designated initializers. And the contract that they adhere to is that every object that gets initialized is going to go through one and only one of the designated initializers at each level of the class hierarchy.
Okay? Which means that if you want to to do initialization, what you need to do is you need to override all of the designated initializers of your superclass, because that way you're guaranteed that your initialization will happen. So in particular, Here's an issue that people run into and why this fixes it. Interface builder will create objects in a variety of different ways. Under some circumstances, it will use a NIT with frame. Under some circumstances, it will use a NIT with coder. Under some circumstances, it will use a NIT. These situations are actually documented and you can learn them if you want. I mean, I suppose it's always good to know things, but if you override all of your designated initializers, it doesn't matter how the object was created because it's always gonna go through one of them. That's the contract. So I suggest you do that. And this pops up on the mailing list sometimes. Okay, we also need to... tear down our observations, which we can do in dialog when the object is being destroyed, not under garbage collection. And then under garbage collection, we do also need to tear them down there and finalize. This is a little bit of a difference from the NS Notification Center. You may know that if you're using NS Notification Center notifications, you do not need to unregister yourself and finalize. It will sort of happen anyway.
That's not true with KVO. You need to do it explicitly. Okay, but then there's something else to do here, which is that you may have heard that it's not really a good idea to have a lot of code in DL or especially in finalized. And that's true for a variety of reasons. But let me get to those in a minute. For now, I want to say almost from a performance point of view, I mean, display doesn't really make a lot of sense unless you're actually in a window. So instead of doing it with DL, something we can do is we can implement the method view will move to window, which is a method of NS view. It's very useful. And we can just Well, we're being told that we're moving to a window. We're going into a window that exists if the new window is non-nil. We're already in a window if when we call self-window, we get something non-nil. And we only need observations when we're in a window. So if we're currently not in a window and we're going into one, then we need to set up the observation. And if we're coming out of a window, we need to tear it down. And this is nice because, yes, we do get to remove the Nialic code.
But it's also, like I said, good because Nialic and especially Finalize are, um, are a little bit dangerous in some ways. So you don't know when DLX is going to get called, if at all. It's not really a place where, because anybody in the entire operating system can retain your object. You don't know who's going to do it. The framework will do it unpredictably. So really, you want to reserve DLX for use in just releasing memory, and that's it. This isn't exactly memory. This is something a little bit different. So if we can arrange to do it in a more deterministic fashion, that's a good thing. And finalizing, I'm going to talk about it a little bit more later, but yeah, it's also good to avoid. All right, so what else do we have to do? Yeah, so all of that so far has been infrastructure. It may sound like a lot, but I'm gonna look at the code. You'll see it's really not that bad. And then this is the part we actually care about. So we still have to have our drawRect. We do have to implement this method, key values for paths affecting display.
And then we have to synthesize our actual object, and that's it. So now this is the part of the code that we consider to be important. Okay, so what do we do if something goes wrong for debugging? in the debugger, we can say PO, which stands for print object, or poo out, as somebody in my group likes to say. The observation info for the object. So this is something that is not actually, it was documented in the 10.4 AppKit release notes, but we recently discovered it didn't actually make it out to any other documentation, so probably it's good to mention this.
The method is documented, and there's a setter for observation info, but it doesn't, it's typed void star, and the reason it's documented is it says that you can implement the storage for this as an optimization. You can give the thing a pointer, a place to store whatever this observation info is, which is completely opaque to you. And the reason we do that is because otherwise we have to go through a global map to find it, which is a little bit slower, has concurrency issues and such. It can tend to slow things down a little bit. So that's that. So if you're trying to debug, oh, and I should say what it does. Excuse me. So what this does is that it shows all of the people who are observing your object.
Okay? So that's great. Normally you don't get the information in that direction. It's hard to figure out who's observing you. And certainly we don't want you to try to figure out what this object is and parse it. It's really just a debugging aid. But it's a very nice debugging aid. So do that. Then, okay, so having done all this, I'd like to actually take a look at the code. If we could switch to the demo, please. Just to show that this, so far it may have actually seemed like a lot of work. And it's not, it's just that I've been talking a lot. So let's see what it looks like.
So up at the top, we have the part of the code that I was claiming that we care about. So the drawRect, the keyPaths for values affecting display, and then the synthesize attribute. Then we have some stuff that I'm skipping because I'm gonna come back and do that in a minute. And then we have the infrastructure part. So we have the declaration of the new context, We have the part where we set up the auto display. So actually let's just count them first. So it's just four methods and all of them are pretty short. None of them are more than six lines of code. And they're really only six lines because the spacing on the screen is so funny. So we set up the observation, we tear down the observation with removeObserver. And then when the view is moving to the window, we want to do the setup sometimes, tear down sometimes. And then we have to actually implement the method that gets called when the values change, check the context, and actually invalidate the display. Okay, so that's that. And that should work, so I'm gonna run it. Gorgeous, eh? Woo. Okay.
But then, so obviously this is, we didn't save a whole lot here, right? Because, I mean, we didn't have to implement an accessor. Instead, we implemented four methods. That doesn't seem like a huge win. But the win, the nice thing is that they're totally generic. So now if we want to change things, we can do that pretty quickly. So I'm just going to comment this out.
Let's say we wanted to draw a gradient in the window instead, okay? So we would need to, instead of having just a single color, we want to have a top color for the gradient, a bottom color for the gradient, and let's put a title in the middle of the thing too, then I don't want that accessor, that attribute, excuse me, I want these three instead.
and I'm going to comment out this chunk of code, and instead I'm going to say, okay, I want to synthesize those three new properties, excuse me, synthesize, and I want to say that whenever any of them changes, I would like my display to be invalidated, so there they are, just written in a list, and then I want to actually write my drawing code, and this is longer, but the whole point of this is that, I mean, this is what the view's supposed to do. This is the part that I hope you're somewhat interested to actually write because this is what you're doing. I mean, we're getting the bounding rectangle for the view. We're creating a gradient using the top color and the bottom color.
And then we're drawing the gradient within our bounds. And then the second part for the title here, what I'm doing is I'm demonstrating something else here, by the way, that's why I'm going through it. Then we're getting the title, we're getting the size of the title. That's you do that with bounding rect with size, using the text.
And then we're going to center it more or less in the view, but because the centering can cause it to be non-pixel aligned, we're going to use this method centerScanRect, which will align the box that you've just made to pixels in the view. And this is particularly interesting because it works well at high resolution. If you're aware that we're trying to get things working for resolution independence so that we can run our displays at higher scale factors so things just take more pixels on the screen. So if you're used to just rounding and saying, if you want to make things integral and make them look snapped in the right place on the screen, that's not going to work right at high DPI, but center scanner act will. And it's actually not really necessary for text. In fact, it's probably better if you don't do it, but I'm just demonstrating. So that's that. So we can do all that. We run it. And again, now we find that we have a gradient. The top and the bottom color can be changed. And it all updates beautifully, and if I draw a little bit of text in there, then it updates. Okay, that is the demo. Could I have the slides back, please?
Okay, so what was the point of that? So the point is that, yeah, when we first started, it didn't really seem all that worthwhile, but as we continue to work with it, we can continue to make use of the code, and it doesn't really need to be rewritten. It can just sit there. In fact, this code was not written for this session. This is code that I have in my personal toolbox because I have to write a lot of test applications to see why things aren't working.
And I have this in a Vue class that I typically subclass whenever I'm writing a new Vue. and I use this pretty often. Let's also discuss the trade-offs a little bit because this is not without peril, I guess. So it makes it harder to tell what the accessors do. Before, you could just read straight down and say, okay, this is what the set method does. And now it's been indirected. It's been gathered somewhere else. On the other hand, it's been centralized, too. So in some ways, it may actually make it easier to read because you've just got this row of synthesized attributes and then elsewhere, you have just a single method that says, oh, by the way, all of these guys, when they change, they're going to need display. And so there's a trade-off there, and you're going to have to think about it. It's not an unusual trade-off to see in Cocoa, because we often do things like this. Just think about the way delegation works with views.
I mean, that's a way of saying somebody else is going to do part of my logic for me, but what we find is that often that delegate object, which is some sort of controller, is often the controller for multiple different views or multiple different portions of the system, and it actually ends up centralizing that information and making it easier to read, because it's all in one place.
And that's the same kind of thing you see here too. So I think in the case of this display stuff, I find it to be a pretty good trade off. I don't think it hurts the readability of the code, especially because the fact that it invalidates display is usually not important information. It's just something you want done. So yeah, but then, yeah, I think it's a good trade off.
Okay, phew, for a minute I thought that stopped working. But then, and yes, you can use this as a general technique. You can observe self and you can do anything you want in one of these when you see the property change. But if you were just using this to avoid, sort of on a per property basis where, hey, when I see my property foo change, I'm gonna do this other thing by observation, that seems pretty confusing. So you probably wanna avoid that.
Last, I did just want to point out center scan rect, because I don't think it's very well known. It's been in the operating system since 10.0, but nobody's had a reason to use it until recently. So you might want to take a look at that. Okay, that is most of what I have to say about that. But I would like to take a little moment here to talk about some garbage collection issues. So in particular, when we were using that KVO context, the context is typed void star. All right? And what we passed in was not a void star, but it was a string. It was a static string. So this is going to have some repercussions because our garbage collector does not scan through all of memory and keep every single object alive. That would be a conservative collector. What ours does is that it knows how things are laid out, and it only looks for possible objects within variables that are typed to be object type. So when you have this void star, if you're passing something in there, our system is not going to know that that's an object if it is an object, and we're not going to keep it alive, which makes it unsafe to pass objects there unless you take extra care to make sure that it is safe. So what are the various different ways you can make it safe? Well, you can use something that you're pretty sure is not going to go away. So in the case of the static in a string, there are two reasons why it's not going to go away. First of all, it's stored in a static global variable. That's enough. Another reason it's not going to go away is because it's just constant. I mean, @quoted strings, unless you're doing something fancy with bundle unloading, never go away.
Okay, one thing people sometimes try to do is they want to pass self-class as the context. I suggest you not do that. That is too generic. It's important when you choose the string to choose something that nobody else might try to use as an observation context. And your class maybe doesn't satisfy that property. And also, even if you do self-class, that's not getting your class. That's getting whatever class is currently active. I mean, it might be a subclass, basically, if somebody has subclassed your code. So that's not very useful. Another thing you can do, and this is the part that I really wanted to talk about, is you can CF retain it. Because under garbage collection, what CF retain means is it means, hey garbage collector, something outside of the scope of the things that you know about is using this object. So you cannot destroy it. It's a hard retain.
But what I want to say about this is that it is harder to get right than it looks. And I suggest you not do it. So first of all, something which you may or may not realize is that when you're not running with garbage collection, CF retain, which is the core foundation retain function, I don't remember if I actually said that, is absolutely interchangeable with the retain message in foundation. To the point where you can take any CF object, say a CT font raft, a core text font descriptor, which is not bridged to any Objective-C type, you can auto-release that if it's useful because it will eventually end up sending it the release message, and the release message is equivalent to the CF release function. So they're completely the same thing when you're not running with garbage collection. However, when you are running with garbage collection, like I said, they are different. You can't mix them. You need to keep them balanced independently. And it has subtleties. So suppose you have something like this. Suppose you have a little object network of objects referencing each other.
Well, under garbage collection, this is fine. If nobody else is referencing the little network, then it will be seen to be unreachable, and all of the objects will eventually be destroyed. Now, suppose that you replace one of those references that you think of as being, you know, I'm using this thing, with a CF retain. Well, suddenly you have a problem. This is not going to work. This section of the graph is going to leak. And a good way to think of this is that the CF retain, it's not directed. The system does not know that it's this thing, CF retaining this other object. It's just this global reference that says keep it alive. And so you're in this kind of situation where the collector is not going to be able to tell that the object needs to be destroyed. And in particular, CF release and finalize, that's when it's going to get you into this kind of problem because finalize is never going to run because the collector never collects your object. So when is it okay? So it's not like you can't use it. It's okay if you're pretty sure the CF release is going to happen. So if the CF release is not in finalize. So, for example, if you have a block of code, and at the top you CF retain it, some object, then you pass the object off somewhere in code that you feel like is not necessarily going to keep it alive or whatever. I don't know. And then at the bottom of the method, you CF release it. That's fine because you're sure that CF release is going to run. It's also okay if you can be sure that there aren't going to be any cycles created, meaning that if the object that you're CF retaining does not have any references back up into arbitrary parts of your object graph. If it does, you're really asking for trouble. And leaks in garbage collection are kind of bad because they tend to leak really large parts of the application graph because almost everything is a strong reference. So it is going to cause leaks if you have your cf.retain and finalize. And if the cf.retained object is too complicated to understand, if you don't know what it is, If people are passing arbitrary objects, you really don't want to be CF retaining them and CF releasing them and finalize.
Okay, let's go back and just, that's all I have to say about garbage collection, but let's crystallize what we said. So first of all, a static and a string that you use a fairly, you use something that's specific to your situation makes a good KVO context. I suggest you do that. Secondly, CF retain.
Use sparingly in a garbage collected app. It's useful. It does something that you want, but not all the time. And then avoid implementing finalize. This is just one of the ways that finalize is more subtle than it originally looks. So you should be afraid of any CF release you see in a finalized method. And you should be afraid of other things you see in finalized methods, too. The big difference is that dialog... happens in a more determined order. If you have some tree of objects that are being deallocated, you can be sure that the root of the tree will be deallocated before the leaves if it's releasing the leaves as it goes. With garbage detection, that's not true. The whole tree is found to be on reference at the same time, and they'll get finalized in some arbitrary order. And you shouldn't be trying to message those other objects that are being finalized at that point.
And particularly if you accidentally store one and two, you'll start getting resurrection errors. If you see in your code that you're getting, that things are complaining about being resurrected, it's very likely because you've got code and finalized that's too complicated. Okay, that's enough. So we talked about key value observing with this color property. Now I'd actually like to go back and talk about making it bindable, okay? So what do I mean by this? So what's bindings again? Bindings is a way of establishing a two-directional connection between, say, a view and a model, okay? And what you do is you say, you say, I'd like to bind some property of the view to the model.
And what it does is, yes, same as before, the view is going to observe the model, and whenever the model changes, the view is going to get updated. But instead of receiving a notification of the update, it's true, it does receive a notification, but you think of bindings as being from the outside, it actually does something with the notification. In this case, it would update its view.
So you see we're almost there, right? But then you also think of bindings as being a two-way connection, so that if the user comes along and changes the color in the view, which in our code is not actually possible, because we haven't implemented anything for that, then it gets pushed down, oh, excuse me, Did I miss something? I thought I did. It gets pushed down to the model is what I wanted to say. Oh, okay, I guess it just happened automatically. All right.
So it's a two directional connection that uses KVO. And we're almost there. So in our case, it's fairly clear that it would be nice if this color property were actually bindable. So what's going to be involved in implementing a custom binding for this object? Well, it actually turns out that nothing is involved. This is already bindable. We've already written all the code we have to write.
So that's worth noting because custom bindings are sometimes thought of as being difficult. And there are cases where they're difficult, but there are also cases where they aren't. And during this talk, I'm going to discuss the easy case, and I'm also gonna discuss one step up from the easy case and show that really writing custom bindings is not such a big deal. But first, so let's just see how you end up using the binding. Okay, well, this is what we might do. We might take our color view, we'd bind the color binding, that's what we define, to the user defaults controller, user defaults being the object which manages the preferences, all of the plist files and library preferences. And the key path happens to be values.color. That's specific to the way user defaults works. And then we have a transformer, keyed on our card from data transformer name that we're passing as an option for the binding. What does that mean? Well, the only object you can store in defaults are of six different plist types, I think. It's string, number, array, dictionary, date, and data. And that's it, NSData. But, and a color is not any of those. So you cannot directly store a color in the preferences database. However, it's fairly easy to transform a color to a data using the fact that it conforms to the coding protocol. So if you put it through a keyed archiver, what this code is doing is it says every time you need to update the model from the view, first run it through the keyed archiver to make data. And every time you're pulling data out of defaults, run it through the keyed unarchiver to produce the actual color. Okay, great. Oh, right, so I was saying that some people think of implementing custom bindings as being difficult. Well, the converse of that is that some people, before they've looked at it very much, also think of custom bindings as being extremely easy because they think it works like this. They think that you should make your view property observable, you should make your model property observable, and you're done. And that's not true. So the thing to understand is that bindings are actually asymmetric. The view observes the model.
That's true. And whenever the model changes, the view gets this notification and it updates appropriately. It gets observed value for keypath. But the other direction, that's not how it works. When the view needs to update the model, it just does it. It directly messages. I haven't used an observing line here in the direction from the view to the model. I've used a hard line to indicate that it just has this thing, it just uses it directly. So what this means, however, is that the easy case, the reason why we fell into this before, is that we didn't have any need to update the model from the view.
Data was never flowing in that direction. It was always flowing in the direction from the model to the view, which meant the KVO stuff was just working. And on top of that, it was working because of NSObject's implementation of the bind method, which I'm going to go through.
So NSObject implements bind. What it does is that in the bind method, it starts observing the model. Okay, fine. And it passes its own context. No, we're going to keep that in the back of your head. Okay. Then it implements observed value for key path, and when it gets the context that it expects, it dispatches another message to itself, which is set value for key with the value that got pushed up from the model. Okay. And what does it use as the key path? It uses the binding. thing. So that means that when under under certain conditions that means that's enough whenever the data is always flowing from the model to the view and you've made a binding whose name exactly matches a property in your view then you're done.
Okay, and I did want to point out, of course, this means NSObject really is doing some observation of almost anything, and if you don't check your contexts in the observeValueForKeyPath method, then you're gonna block it, and it's gonna stop working. So be sure to call super in observeValueForKeyPath if you don't recognize the context. Then the harder case is when you do sometimes need to update the model using the view.
But we can handle that too so here's our plan for doing that so first of all there we have to handle both directions of data flow okay but let's let ns object handle the data flow whenever it's coming from the model to the view and what that means is that we're either not going to override bind. Or we're going to call super.
Then when we need to push data down, we're just going to do it. And we'll do it by using the info for binding method to find the data to push down. Maybe I should say before I get too much further also that... The way this works, sometimes you think of bindings as aliasing two properties.
You have some property in the view and you've got a property in the model and you think the bindings makes them the same. That's not really true. It's more like a default implementation of a delegate. And if you think about the way delegates work or target action or any of that, it's not that they're in sync. It's that sometimes the view says, you know, this is a good time to update the model. Or with the tech system, it's not like you get every single character every time somebody types. and you don't get, because you wouldn't want it, because you only want the text when the user's actually done. Bindings works the same way. It keeps its own copy, and it just updates the model at appropriate times. It's just exactly like a delegate. Okay, that said, so let's, as an example for a custom binding, let's use Sketch. And could I have the demo machine, please? Thank you. Okay.
Okay, so Sketch is an application that is, as an example, it ships in developer/applications whenever you've installed the developer tools, and it is a little drawing program. So I'm going to show it off to start. So you can make circles, you can make big rectangles, you can color them, salmon, evidently.
But then what I wanna talk about, that might be a bug or something, I don't know. What I wanted to mention is that it has a rather nice class in it, which I'm going to talk about, which is this scroll view here. You see the pop-up down in the corner? It has a zoom property, so you can zoom in on the thing. And it uses bindings to do the zoom property. And it falls into the easy case, actually, because the scroll view, there's no way to update the zoom from the scroll view itself. There's just also this pop-up button, which can control it, but the pop-up button pushes data down to the model, and then the data pops back up into the view and actually updates the scroll. Okay, that's great. That's how that already works. I was saying that's the easy case. Why, however, am I saying that we're going to do the hard case in Sketch? Well, something that might be nice with this is that in 10.6, we have support for gesture events.
In Mac OS X, the same as you have on the phone. So if you have a new model MacBook Pro or MacBook Air, such as the one that I'm using right now, then if you make a pinch gesture on your trackpad, then you can magnify or back away from whatever you're looking at. So it would be really nice if our scroll view had that ability, but when we add that ability, it's gonna knock us into the hard case because we're gonna have to start having to, we're going to need to be able to push data from the view into the model. So that's what we're gonna do.
First, let's just take a look at what the code looks like for now for the existing binding. And I want to go over that in my slides too in a minute. So there's plenty of stuff in Sketch, of course, but most of it we're going to ignore. But let's look at the bind method. So it's sort of interesting.
What it's doing here is it's grabbing the it's taking the the the say somebody tries to bind this property which is a scroll view zoom factor okay. Then it binds the pop up button and then it also calls super so if I could switch back to the slides for a minute I'd like to diagram what's going on.
Thank you. Okay, so here's what happens. So when somebody comes along and says, scroll view, please bind your factor to the model, the scroll view has this internal pop-up instance variable, basically, that's opaque. The model doesn't know about it. So you would think that that means that the scroll view, it does need to sometimes push data down to the model because the user can manipulate the pop-up button. But what it does is that rather than directly messaging its pop-up, when somebody asks it to bind, it first tells its internal pop-up variable to bind to the model, and then it goes ahead and calls super, which means it's taking advantage of the NSObjectLevel implementation of bind in addition to having bound the pop-up button so that whenever the model changes, both the pop-up and the scroll view are going to update. This is a little bit unusual. It actually means that the scroll view is bindings only. You can't directly tell the scroll view to change its zoom factor because it would not update its pop-up.
Sort of strange, we would not do this in the app kit because it's not generic enough, but there's no reason why you shouldn't do this in your own code. I mean, your code doesn't have to handle every case in the world. It just really needs to do things that, it needs to be expandable to whatever cases you think might be useful in the future. And for now, just do whatever you need, as long as you can document it clearly enough. So that's the way that works. Could I have the demo machine back again, please?
Okay, great. So that's that. So like I said, what this method does is it really just creates the pop-up button if it isn't already there. It binds the pop-up button, and then it just calls super unconditionally. And that's how that works. Okay, great. So now let's go ahead and actually implement our gesture support. So I have my pop-up that I did with pragma mark for event handling.
The reason there wasn't anything there is because it was commented out. So let's go ahead and uncomment it now. What happens is that when the user makes a pinch gesture or something that's supposed to be able to change, something like a zooming on your view, and its application and its window will dispatch this method to you, which is magnify with event. It's very easy to use. And what you can do is you can pull the magnification out of the event, which is a change in percent. So if you get something like 0.25 as the magnification, That means increase your zoom factor by 0.25. So if you're currently at 100% and you get that, then you want to go up to 125%. If you get minus 0.25 and you're at 100%, you want to go down to 75%. This, we luck out, and it's exactly how Sketch models it in the first place. There's a factor instance variable, which is the zoom, which is the scaling, and what we want to do with the magnification is we just want to add it. So if you're at 1, it goes up to 1.25 or down to 0.75.
However, we do have to cap it because the app kit is not smart enough to know, hey, this is already zoomed in all the way. You can't go any further. I mean, it might start sending you minus 0.25 events when you're already already down to zero. So I have a method up here, which just caps the scaling to clamps it to some reasonable values. So it has both a min and a max, and it makes sure that we don't exceed them. Okay, so then we have our new factor, and then we need to set it. Now, because of the way the scroll view works, like I said, because it's actually bindings only, what we can do is we can just update our model and let those changes bounce back into both the scroll view and the pop-up button. And that's what I'm going to do in this method, set value for binding, which I just made up myself, and I probably should have given a prefix because that's too generic. So don't do that. Okay. So how does it work? So you grab the binding info for this binding, whatever it is. Okay. If it's not nil, that means you're bound. If it's nil, something weird has happened, and we just want to ignore that. Actually, I guess it might not be weird. It might just mean you're not bound. We'll pull out the observed object using this documented key. You ask the binding info for... This is just a dictionary. You ask it for its observed object. Okay, great.
You ask it for the observed key path. Fine. All right. And then we just need to update it. So we will say observed object set value for key path. That's going to update the model. And that's all it takes. And then that will, because of the KVO observation we're doing, will always bounce back into both our scroll view and the pop-up button. So let's just see it. We're actually done. So if I make this guy, and I make it some nice color, and you'll have to take my word for it that I'm actually going to start using the fancy gesture events, but there they are.
So it works really nicely. OK. And that's it. So yeah, pretty easy. I hope that that made sense. Okay, the one thing that I did want to mention is that there are a number of options that people use when they're doing binding. I, in fact, already showed one. I showed using the value transformer name option to say what transformations we want it done. And when you're pushing things from the view to the model. And I haven't done that. So I haven't accommodated any of those options. And the reason I've done that, I haven't done it, is because it's too hard. Because there are a lot of options. There's a value transformer, there's a value transformer name, there's any number of things, you have to handle them all individually, and there's no way to expand it in the future.
So really what I'm saying is that again, this is a case where just look at what, you don't need to handle everything completely generically all the time. Just do what your code needs to do. And because we don't need any fancy value transformers at the moment, I'm just not doing them. and I'll do them as necessary. And hopefully maybe AppKit can try to, in the future, do something that is sufficiently generic that would be able to handle all the options for you, but it's not there yet. So this is probably what you ought to do. Could I have the slides back, please?
So I'm getting done. So my tips for this section, I want to just go over what I've talked about. So first of all, you should probably steal this class if you think it's useful for you, because it's a really nice class. I mean, it handles all of the scaling and the zooming for you. You might have thought that... Oh, actually, I guess I didn't say that. You might have thought there was extra code somewhere to interpret that scale factor.
That's not the way it works. Coco's view system already has the concept of transforms. affine transforms from every view to its subviews. And that's all that's going on here. So the scroll view just in and of itself is increasing the magnification for all of its subviews. So you don't have to write any extra code in the subview to make it zoom like that. The scroll view handles all of it, which is why you might want to consider stealing it. And this is also the way resolution independence works, by the way. We don't have to change Cocoa a whole lot in order to accommodate higher resolution displays. We just have to fix bugs, basically, because it already has this infrastructure that unties the pixel resolution from whatever the local coordinate system is. Those can be independently varied. Yeah, okay, so then let's talk about custom bindings. So again, custom bindings are easy whenever your data is only ever flowing from the model to the view in that direction, okay?
And its object will handle all of that for you. In the slightly harder case, when it's just a simple 2-1 property, and you can have, and the binding name, actually, I didn't say bindings can be very abstract. They don't have to have anything to do with properties. I mean, you'll often see lots of things in the app kit have value bindings, and there's no value accessor on almost anything in the app kit. So bindings and properties don't have to correspond, but when they do correspond, that's an easy case, and you can, it's not so bad.
You can manually push changes down when you want to. And that's my talk pretty much for this morning. So just in brief summary, what are some of the different areas we covered? We talked about key value observing. We used an application of that to let us use synthesized accessors more effectively. We talked about custom bindings. We talked about garbage collection. Be careful with CF retain, but also be careful with just void stars. Things won't automatically stay alive. Unfortunately, with garbage collection, you can't completely forget memory management. It just does a lot of it for you. And then we talked about a little bit of drawing. Then there was some specific code, which you might be interested in. There was the auto-displaying view properties, which I hope would actually be of real utility to some of you.
And then there's the SKT zooming scroll view, which, like I said, can just be pulled out and used in your application if you like. For more application, you can email Deric Horn, who is the evangelist for Cocoa and lots of other frameworks on the system. You can look at our documentation, which is easy to find, of course. Just search for Cocoa. Today happens to be Cocoa Day. at WWDC. So there's lots of other sessions if you'd like to go on and learn a little bit more. Right in this room, right after this talk, there's going to be a more advanced session for using Cocoa animation, the reflection of core animation in Cocoa using all of our nice support for that. We're going to talk about performance techniques later in the day. Ali Ozer is going to talk about concurrency and mostly concurrency, but lots of good stuff with that. Then there's going to be a session on polishing your Cocoa application, which is in some ways a lot like this talk. It's sort of a smorgasbord of just stuff you can do, but it has a little bit more focus on how do you make it really nice? What are some of the features you can do at the end? Say performance tuning a little bit maybe, or accessibility scripting, things like that. Also, if you feel that the problem with this talk is that it was insufficiently similar to musical theater, I also suggest you check out that talk.
And then we're also going to talk about performance in document-centric applications. that's going to talk about things like NSFile wrapper and a variety of issues. I think that's a good talk, too. That's a very good talk. You should definitely go to that if you don't feel like you have something else you really want to go to. And then last, we do have a lab today at 12. Everybody in the Cocoa group, except for those who are giving talks right after, are going to be there, which means it's a really good time to come ask us questions. So please do. These things won't automatically stay alive. Unfortunately, with garbage collection, you can't completely forget memory management. It just does a lot of it for you. And then we talked about a little bit of drawing.
Then there was some specific code, which you might be interested in. There was the auto-displaying view properties, which I hope would actually be of real utility to some of you. And then there's the SKT zooming scroll view, which, like I said, can just be pulled out and used in your application if you like. For more application, you can email Deric Horn, who is the evangelist for Cocoa and lots of other frameworks on the system.
You can look at our documentation. which is easy to find, of course, just search for Cocoa. Today happens to be Cocoa Day at WWDC, so there's lots of other sessions if you'd like to go on and learn a little bit more. Right in this room, right after this talk, there's going to be a more advanced session for using Cocoa animation, the reflection of core animation in Cocoa using all of our nice support for that. We're going to talk about performance techniques later in the day. Ali Ozer is going to talk about concurrency and mostly concurrency, but lots of good stuff with that. Then there's going to be a session on polishing your Cocoa application, which is in some ways a lot like this talk. It's sort of a smorgasbord of just stuff you can do, but it has a little bit more focus on how do you make it really nice? What are some of the features you can do at the end, say performance tuning a little bit maybe or accessibility scripting, things like that. Also, if you feel that the problem with this talk is that it was insufficiently similar to musical theater, I also suggest you check out that talk. And then we're also going to talk about performance in document-centric applications. That's going to talk about things like NSFile wrapper and a variety of issues. I think that's a good talk too. Actually, that's a very good talk. You should definitely go to that if you don't feel like you have something else you really want to go to. And then last, we do have a lab today at 12. Everybody in the Cocoa group, except for those who are giving talks right after, are going to be there, which means it's a really good time to come ask us questions. So please do.