Cocoa APIs in a Swifty world
Cocoa; from Objective-C craziness to Swift-friendly
Hey there everybody. It's been a long couple of days here at the Swift Summit. There's been a lot of really awesome swiftiness going on today and yesterday. The thing is is that we are iOS and Mac OS developers, and a lot of the things that we use are frameworks that are provided by Apple. One of the biggest collections of frameworks, and libraries and things is called 'Cocoa'. So, the thing is we've been doing all this stuff in Objective-C for so long, and this conference should be inspiring you to go ahead and rewrite all of your code and take out all that Objective-C garbage. So, it's really easy. First of all, you just use the Crash Operator everywhere, make sure you force unwrap everything, no problem, and then if object is not equal to nil, then you're clear to go ahead and do your stuff- all the optionals. Then Pyramid of Doom all of your functions, make sure you have all that deep nesting because that's really, really useful, and just go ahead and rewrite all the code because that's how we do it. What could possibly go wrong?
01:12: Nothing, yeah. So, just translating Objective-C to Swift using Objective-C paradigms in the thought process is actually the wrong way to do it and I'm sure a lot of you know this, but in case you were unclear, you could actually just put all of your code into a nice tool on a web and use this website. All you do is just copy and paste your code here and out comes some really nice Swift code. This is real, objectivec2swift.com. Check it out, production quality right there. Awesome, and that's all there is. Cocoa APIs, we're done. No.
Even though we've got this new awesome language, Swift, a lot of this stuff that we write for on iOS and Mac are the Cocoa libraries (...), and they're written in Objective-C...
Right. So, like I said, we write stuff with Cocoa. Even though we've got this new awesome language Swift, a lot of this stuff that we write for on iOS and Mac are the Cocoa libraries that we've known that are stable and solid. Yeah, they're written in Objective-C and they have some of that old baggage, but it doesn't mean you can't use them in Swift and you should embrace them. So what we're gonna do, I've got a really simple example to walk through, something that maybe you've used, maybe you haven't, something called 'NSTimer'. Do you use this?
02:22: Is it like the best API ever? Yes. So we're gonna take it from the Objective-C craziness and we're gonna make it a little bit more Swift-friendly. So first of all, let's take a little time to go and check out the docs as every one of you should be doing using Dash by the way instead. We see that we've got this thing, this target-selector paradigm going on here. That doesn't really fit very well into Swift. Swift is much more functional. We don't have that dynamic nature, so how are we gonna use this? It's gonna be a little awkward, but we'll get there. First of all, so let's start using it. So we know how NSTimer is, we know how we need to initialize it, we know that we have this target selector thing going on. But we want to use a struct. Struct is the preferred way to kind of put our classes together. So we wanna use value semantics over reference semantics, things like that. So we wanna start with the struct. I'm gonna add a timer. We're gonna just use this as a property so that we can have a reference to it, and then we're going to go ahead and initialize it with the way that the docs say that we should. So we've got a selector, it calls a particular method, and right away we already got a problem. If we do this in init, in a struct, this says that, "Well, I can't reference self within the initializer until everything has been initialized. The timer needs to be initialized in this case". This doesn't really help.
So we're gonna take it from the Objective-C craziness and we're gonna make it a little bit more Swift-friendly.
Okay, so I'm gonna compromise a little bit, I'm gonna change it to a class. It's not what I wanted, but maybe this will help me get going. Nope. So this gives me another error. This says that we're using self-timer again before we're being initialized. We're also using a different parameter, so it's not really helpful here. So we're gonna compromise again. We'll just call and have another function that we can call. So now we have no problems with the init function and now we can just start watching things, and this should be okay except when we start running it, we have this class error here and this says that, "The class has no initializers because timer is not initialized." Well, I tried that. It just said it didn't work. Okay, so... Okay. Never mind. Never mind that. If we get past of all of that, now we have fired and we've got our method here because that was missing. I didn't add that before. So we have a fired function and when we try to run this, this doesn't work. Why? The function is right there. I just added it. Turns out, you need that dynamic key word that you never use. That's the only way to get something with the target-action paradigm to actually work in your class.
04:58 Saul: So this is okay, but this is a lot of mess and it's not really Swifty, and if you're doing all of this on the classes that you're trying to do something with, it's a lot of mess that you really don't want to deal with all the time. So let's start off and rethink it with something the way that we think that it should work. It should be really this simple. Like if I wanna watch something during the... inside the constructor, inside the initializer, I just wanna do this. I wanna have a timer and I want to start, and whatever I want to do when it's fired, and I want to use blocks rather than this target-selector action thing going on. So let's start with this in mind. This is the end goal. So what we have here is we have a timer here and we have a property. One of the tricks that I like to do with timer... again, this really awesome API, if you have to invalidate it when you're not done, you also have to add it to a NSRunLoop in order to actually start the timer running. So you have to do this and the way that I do this in Objective-C is do it in a property and do it when you set that. So automatically do that. You can do it here with these property observer things in Swift. Really simple. You'll notice the crazy double arrow dash operator. I stole that from Runes the library there, it's a really simple thing to use for flatMap operations, gets rid of those nasty if statements if you really don't like those.
So, next what we want to do is we want to create the start method and you'll notice in here that I've just created... I've got a reference to my timer and I've got some default properties, and I've got a callback parameter here that I can use, but you'll notice also I'm using NSTimer as the initializer but it doesn't have this callback constructor by itself. It's just not built-in. So what we have is we create an extension and create a convenience initializer on NSTimer, the object itself, and we go ahead and add the extension for us. Now you'll also notice that the target here is self and we're doing a fired method so we're gonna do it on ourself. But the thing to notice about that target property, that parameter, is that it retains a strong reference to the parameter in the target. So when you specify a target, it's gonna have a strong reference. That means you're gonna have a retain cycle in this case. We don't want that.
07:12 Saul: So let's do something else. Let's go and create another object, just kind of a little helper object, just some kind of container. Now this is getting really small here so all we have is something that holds on to a block and it also fires the block every time that method is called, the fired method. So that gets triggered from the timer. Really simple. Now what we can do, we can put this inside of our extension, hide this from everybody. This is an implementation detail. Nobody else needs to know about this and we have all of the things here. You'll notice now that the target is that CallbackContainer and we initialize that, we created that inside the constructor with the callback that we get from outside and what this does is it... Since the NSTimer parameter for the target holds on to that, this doesn't go away. It's not just the allocator right away. So this kinda works and voila. We have that.
We've taken a Cocoa API that was kinda old and crufty. We've not had to worry about whether or not we put it on the right NSRunLoop, whether we invalidate when we stop. We have a really nice simple way to start it, and we have some nice Swifty callback APIs. So the thing is, what makes this Swifty? This is just one example of how to make a Cocoa API Swifty. But what are the properties that make this Swifty? So, the first one is that you're using value semantics. Value semantics, having things copyable, using things like that are a very powerful feature in Swift and a lot of times, we want to do that versus the class semantics which we had to make a compromise in our first attempt. So we don't wanna do that all the time. The next point is that the API is more Swifty, when it's short, it's concise, the name reads well, it's also got the block callback syntax, it's using the trail enclosure syntax. So the API just feels more natural in this setting, and we're also using immutable objects again because we're using the value semantics.
So, that's been a Cocoa API in a Swifty world, and I guess I'm gonna leave it open for questions. Thank you.
Q1: I've got a question that maybe most of the people in the room just know the answer to, but I don't. I'm confused about using value types for things that change over time. And then your example, your timer changes over time. I once almost used a struct that had a semaphore as a variable in it, and that really seemed like a bad idea given that free copying I think of with value types. Could you help me out here? I know you had structs first and then you had classes. I understand about classes for things that change, but I'm really confused about structs for things that change sort of by themselves over time.
Saul: Yeah, I think the general rule of thumb that I follow is that if something doesn't need to change I'll use a struct, and it's also generally faster to use a struct 'cause they're allocated on the stack. There's a number of ways or reasons that you wanna use structs over classes, but it really comes down to the problem that you have at hand and it's very... It's just, it's very specific to the problem you have at the time.
Audience: Okay, but if I have a thing with a timer and the timer's gonna go off and that's a change of state with time, you'll use a struct or you'll use a class?
Saul: Well, so the timer itself is a class, it's an object. So that thing is going to change over time and it's also just firing things on a struct that's basically reacting to some kind of signal from somewhere else. So the struct might just be recomputing a value. It doesn't mean that's necessarily changing everything in this case either.