This is Cocoa Interprocess Communication with XPC.
My name is Tony Parker and I am a software engineer on the Cocoa Frameworks team.
So, first we're going to start with a brief history lesson.
So, back when dinosaurs roamed the Earth, programming computers, they worked on machines that looked a lot like this one.
They had a single core and a single CPU.
So we made our software have architecture to match, with a single thread.
Now, this architecture worked well for a long time, until something started happening.
CPUs started multiplying instead of just getting faster.
And at that point, our architecture of a single thread for a single CPU started to break down, and we needed to make a change.
And that change of course was a move from single-threaded applications to multi-threaded applications.
And this was a challenge, but technologies like GCD were there to help.
And it wasn't something that our users could immediately see just by looking at the application, that it had made this transition.
But it was still something that they appreciated. Their applications were more responsive, and they took better advantage of the hardware that they bought.
So today we're facing a similar challenging transition. And that is from a status quo like this, where we have a multi-user system.
Now, you see that we have had for a long time barriers between these users.
So Somebody Else can't get to my data, and I can't get to root's data.
But we're finding that today's systems are really used by one person.
And although they are protected from Somebody Else, all their critical files—their contacts, their photos, their personal data—is all accessible by any application that runs as their user.
To understand why this is a problem, let's imagine an OS where every task shares just one chunk of memory.
In fact, some OSes have behaved this way in the past.
So what we'll do is we'll just split it up.
And we'll have each task just pinky-swear they're not going to touch each other's memory, or any data that the other task is using.
Because they're all executing in the same memory space, of course, that's completely possible.
We'll just take them at their word.
Well, clearly that's a bad idea.
What we really want is those barriers back.
And have each of these tasks to have just the level of permission that it needs to do its job.
So we have barriers like this, and they are process boundaries.
So another example, the Preview application.
It's split into multiple processes. And these processes have different levels of permission.
So the one on the left here has access to the hard drive, and access to the network, and the one on the right has access only to the contacts.
So this provides two really key benefits.
The first is one of security, which I just mentioned.
So the process on the right, if it's compromised in some way, either the application or any frameworks or libraries that that application uses, maybe it can access Contacts but it can't access the hard drive and it can't access the network, so the amount of damage it can do, in terms of shipping that information off to somebody else, is really limited.
The second benefit is crash protection.
So if this helper process crashes in the course of accessing the contact information or processing it in some way, then the main GUI application can continue to run, and the user doesn't have to be interrupted with some minor task running into a snafu.
Once we split up these applications into multi-process applications, we need some way to communicate between them.
And this is called Inter-Process Communication, or IPC.
Those dinosaurs we talked about earlier were using things called Mach messages.
Actually, they're still around, they're the fundamental form of IPC on our system, but you may have also heard of other IPC mechanisms like sockets or Distributed Objects or, new in Lion, XPC.
Its job was to simplify the low-level details of this IPC.
It's a C API with custom objects, containers (like xpc_dictionaries and xpc_arrays), and data types like xpc_string, xpc_date, xpc_data, and so forth.
When we introduced XPC, we put it at a low level in the OS X technology stack, at the Core OS level.
And if you had an application that uses Cocoa level APIs, like Foundation or AppKit, then you may find yourself in a situation that we sometimes call impedance mismatch.
So you have an NSArray and you want to send it over XPC.
So you pack it up into an xpc_array_t, and any objects it contains, send it over your connection, and on the other side you have to unpack it and recreate your objects.
So to solve this problem, and to introduce some new features, in Mountain Lion we have a new technology called NSXPCConnection.
It allows you to use your own objects and interfaces with XPC and Inter-Process Communication.
Let's go over a few of the design goals of NSXPCConnection.
So, again, it's Cocoa. In fact, the NSXPCConnection header doesn't import even the XPC header itself.
So XPC is abstracted away.
It's designed to be simple—I think this talk will be enough to get you started.
Because we're crossing privilege and process boundaries, it needed to be secure, and we designed it that way from the start.
It's very opinionated about being asynchronous: just because we've moved from single-process to multi-process doesn't mean we need to revert to a single-threaded kind of behavior.
And all of the above, plus some other features like: not being tied to CFRunLoop, being exception-free (except in case of programmer error, as is the normal Cocoa pattern), and also ARC-friendly (in fact, all of the examples and demos you see today will be using ARC) combine to make NSXPCConnection a very modern, friendly API.
So let's look at a quick demo. I want to show you what one of these multi-process applications looks like, and then we're going to use it as a basis for the rest of the talk.
Okay. So here I have an application called Flight Finder.
Its job is to allow you to put in a destination, pick a date and an approximate price, and search for available flights on that date.
So this application is split up into multiple pieces. And one reason might be that, let's imagine that this flight finder has access to my calendar data to find the next available date on my calendar.
That doesn't sound like something that we necessarily want mixed with access to the general internet. So that would be a good thing to put off in its own little process.
Here is the structure of the application on disk, in my build products folder, the Flight Finder.
Now if I show its contents, you see some of the familiar things here, including your MacOS directory and Resources, but there's this new directory called XPCServices.
So an XPC service is one of these helper processes that we'll be talking about today.
If look at the contents of that, there is another bundle called TicketAgentService.xpc, which is a bundle itself, so I can find its executable if I drill down a little bit further.
So now I'm going to pick a date, a price, and search.
And what we'll see is, in the background, our TicketAgentService has started up.
We see it's here, it's also sandboxed like the main application is, and we found our ticket for a price of $181.
That's a basic operation of the example. So let's go back to our slides and see how we might build this.
So here's a preview of where we're going to end up.
You see we have two processes again, a Flight Finder, the GUI part, and the Ticket Agent, which is the part that actually did the searching for us and returned a result.
We're going to need four major pieces. The first is an Interface, the second is a Connection, the third is a Listener, and the fourth is a way to communicate, and that's Messages.
First up is Interfaces.
The job of an interface is to abstract away implementation details.
And that's not just the details of how that TicketAgentService or the Flight Finder are implemented themselves, but also how the IPC is actually happening between them.
Interfaces provide a clear division of responsibilities, and in Objective-C we have a really handy way of describing interfaces called protocols.
You'll find that protocols form a fundamental piece of how NSXPCConnection works.
So here's the division of responsibilities of our two processes in our multi-process application.
On the Flight Finder, it can get search input from the user, it can talk to the agent, it can show found tickets, and as we mentioned earlier it has access to my calendar data.
The Ticket Agent on the other hand, its job is simply to find flights and perhaps to buy tickets, and return the result back to the Flight Finder application.
So we're going to describe the ticket agent interface with this protocol called Agent.
In this protocol you'll have a set of methods like this one, checkIn. It takes no arguments and has no return value.
But of course, often you're going to want to send data to your helper processes, so there'll be arguments.
This method, buyTicket:onDate:maxCost:, has two object arguments (a string and a date) and also an integer argument.
Now once we've bought a ticket, we're going to need to receive it back in the Flight Finder application in some way.
One way you might do that is by creating another protocol to describe the Flight Finder interface.
Here you see I've got a method called setTicket:, and it takes a ticket as an argument.
But this request-response pattern is so common with these XPC services that instead we're going to let you build it right into the Agent protocol via a reply block.
So you see I've just added an argument that has a Ticket argument as its single return value.
And this reply block will be invoked asynchronously when the reply comes back.
Now we're going to wrap up this protocol in an object called an NSXPCInterface.
And to create one, you use this simple constructor method, interfaceWithProtocol:, and pass in your protocol.
A few more details about these protocol methods: the methods must return void. Again, that's because we're very opinionated about being asynchronous.
Methods that return a value directly are very hard to make work in a pure asynchronous kind of fashion.
So instead if you have return values or reply values, you just put them as one of the arguments to your reply block.
The arguments of the method and the reply block can be one or more or none of the following types: arithmetic types, like int, float, char, long long, and NSInteger; the Objective-C boolean type; NULL-terminated C strings; C arrays that contain those types; and C structures that contain those types, including C arrays; and finally and most importantly, objects.
And those objects must implement the NSSecureCoding protocol, which we'll talk about a little bit later.
So here is our preview again. What we're going to do is create one of these NSXPCInterface objects in the ticket agent.
You see it describes the Agent protocol, and we're going to call it our "Exported Interface".
That is, this is the interface we expect this ticket agent to implement for us.
The flight finder also gets an NSXPCInterface that describes the Agent protocol, but there it's called the "Remote Object Interface".
So this is saying that the flight finder expects its remote side, the ticket agent, to implement the Agent protocol.
So both sides have a clear of what the other side is supposed to do.
Okay, that's Interfaces. Let's move on to Connections.
So I promised you earlier there was an arrow there. There it is.
And there are two objects again, one on each side, called NSXPCConnections.
This connection represents an endpoint in a bidirectional communication channel.
On the ticket agent, there is one key property. This is an object that you implement, and it's called the "Exported Object".
And as you might imagine from the name, its job is to implement the exported interface.
You see here I've declared an object that implements the Agent protocol.
So let's look at how you would do that.
Here again is our Agent protocol that we just saw, and I'm going to create an object called a TicketAgent.
It's a subclass of NSObject and it implements the Agent protocol.
In its implementation, we just implement those methods.
So here's my checkIn method, it took no arguments, so I can just get away with putting a comment there.
But this method, as you remember, took two object arguments and an integer argument and has this reply block.
And this one's actually pretty easy to implement as well.
We take those values as we've received them, and do whatever we need to do to create a ticket object, and then we invoke the reply block with our reply value.
And the way you invoke a reply block is just like any other block, which is like a function.
So this one has one argument, our ticket.
And we're done.
So the NSXPCConnection instance in the flight finder you can create in three simple steps.
The first is you call alloc and initWithServiceName:. The argument there is going to be the bundle identifier of the XPC service bundle that we saw.
The second step is to set up its remote interface. Here again is the same method, interfaceWithProtocol:.
And the third step is to resume the connection.
Connections start suspended so that you have an opportunity to configure them, but you must resume them before they can be used.
The XPC connection in the ticket agent is handed to us, and we'll see how in a second, but it also just has three simple steps to configure: We're going to set its exportedInterface, again using the interfaceWithProtocol: method, and set its exportedObject to be the ticket agent that we just created, and resume it as well.
A few more details about connections. Here in our example we have one side has an exported object, and the other side has a remote object, but connections are bidirectional, so you can actually have both on both sides if you choose.
The lifetime of your XPC service is managed for you by XPC and by launchd, so they're launched automatically when you need them and they will go away automatically when the system needs resources, assuming they're not in the middle of doing some work.
And finally, in this architecture of having an application and a service, you should invalidate the connection in the application when you're done to clean up any appropriate resources.
And we'll see exactly how to do that in a demo later.
So in the world of IPC, it's not always the case that everything goes perfectly, so we have some error-handling opportunities for you.
There's two blocks that are properties of the connection that you can set.
The first is called an interruption handler. The interruption handler is called when the remote side crashes, or closes the connection.
In this case the NSXPCConnection instance is still valid. But because the other side crashed, you may need to restore some state to get back to what you were doing.
So the reason that it's an interruption handler in this case is because this connection was created with a name, or some other way to look up the connection again, so we can just recreate it for you on demand. So you don't need to recreate the NSXPCConnection instance.
On the other hand, you may get an invalidation error, and in that case the remote side crashed or invalidate was called, but we have no way to recreate the connection.
And, in that case, the NSXPCConnection instance is no longer valid.
So generally you're going to see interruption handlers in your application, because your application looked up the service and we know how to reconnect to it, but you're going to get invalidation handlers in your service, because the service received the connection.
So let's talk about listeners, which is how those connections are created in the service.
That's the last object in the ticket agent, and the listener's job is to await new incoming connections and allow a delegate to configure them.
So here's how you do it. This will be in the main function of your service.
We're going to get the singleton service listener, set up its delegate, which is an object which implements the NSXPCListenerDelegate protocol, and like connections, listeners start suspended, so you must resume them.
Now for service listeners, when you call resume, we take over execution of the application.
And from that point on, as I mentioned, we're going to manage the lifecycle. This is equivalent to calling NSApplicationMain, or dispatch_main, or CFRunLoopRun, or xpc_main or any of those kinds of functions.
The delegate just has one method, it's called listener:shouldAcceptNewConnection:.
You see that it has a connection argument, that's where the connection is handed to us in the service, as I promised, and here's the exact same code we just saw, where we configure the connection by setting up its exported interface, exported object, and resuming it.
And then we decide to return YES to accept the connection. If you choose to reject the connection, you can return NO.
The connection has a few properties you can check, like effectiveUserIdentifier and effectiveGroupIdentifier, to see if you want to accept the connection.
So today we're talking about XPC services, which are part of your application bundle and are for the exclusive use of your application, but the same NSXPCConnection APIs can be used to talk to launch agents and launch daemons as well; you just need to use the Mach variant of the init methods.
And one more detail, Mach service listeners do not take control of your application when you call resume, unlike the service listener, that's because in a more advanced use case we expect that you may have additional requirements for how you need to run your launch agent or daemon.
Alright now we just need a way to communicate between these two pieces, and that's Messages.
We saw earlier an exported object in the ticket agent that implements the exported interface, and is responsible for receiving messages that are sent to the ticket agent.
To send messages to the ticket agent from the flight finder, we have an object there called the Remote Object Proxy.
And you see it implements or appears to implement the Agent protocol, as described by the Remote Object Interface.
To get one, you just ask the connection for it like this, remoteObjectProxy, an unsurprising name, and then send it your messages like checkIn. That's it.
Or this one, buyTicket:onDate:maxCost:reply:, you see I've passed in my object arguments and also that integer, and the reply block goes right there inline with the message send.
So that means that it's really easy to correlate any requests with any response action that you need to take, like updating your user interface.
Earlier we saw that you could do error handling at the connection level. You can also set up error handling on a per-message basis.
And the way you do that is by creating a remote object proxy that has an error handler block, using the method called remoteObjectProxyWithErrorHandler:.
And in that block you can take whatever action is appropriate for handling that error. I hope you don't do something like logging Oops! to the console. Users don't look at the console.
A better idea might be to display a placeholder, or perhaps retry the operation if you have an algorithm to do that.
Now you see I've just nested the call to get this remote object proxy with the exact same buyTicket:onDate:maxCost:reply: method we just saw.
So it's important to note that what we guarantee for you is that exactly one of those two blocks will be called per message send.
If you need to for example take a lock before you send this message, you can release the lock in both blocks and know that you're not going to over-release your lock. So that's a pretty handy thing to take advantage of.
Let's look at a little more detail about how this message sending works.
So on the flight finder I send a message to the proxy called buyTicket:onDate:maxCost:reply:, and you see I specified a reply block as one of the arguments.
What we're going to do is, under the covers NSXPCConnection will gather up all of the appropriate information it needs, and we're going to hang onto the reply block until the reply comes back.
The appropriate stuff is sent over the connection to the ticket agent, where we will unpack it and create a stand-in reply block proxy per message, and pass it all off to your exported object.
And there we saw the implementation where we implemented it by calling the reply block with a reply value, like Ticket, and we just do the exact same operation but in reverse, and send it back to the flight finder and invoke your reply block.
These proxies are lightweight object, they're basically just an integer and a reference to the connection that you're using.
They're immutable in the sense that you can't change their error handling blocks after you've created them, so if you need a different error handling behavior, just create another proxy using these two methods.
You can send these to the NSXPCConnection itself, or any proxies that you get from the NSXPCConnection, depending on whatever's most convenient for you.
And an important detail: all the messages we're talking about, that is the ones delivered to your exported object, and the invocations of your reply blocks in the application, are delivered on a per-connection, private, serial queue.
It's not the main thread and it's not the thread that you sent the message on. The reason is because, again, we are trying to promote asynchronous behavior.
So if you need work done on the main thread, then you should figure out what the minimum amount that you need to do is, and then just move it there using NSOperationQueue or dispatch, whatever is appropriate.
So now we have all four pieces we needed to build NSXPCConnection.
Let's go back to our demo and look at it in a little bit more detail. Okay.
So here I have the Flight Finder project.
And you can see it has two targets, a Flight Finder and a TicketAgentService, and in the build phases I've configured it so that the main application is depending on the TicketAgentService, and I have an additional Copy Files phase to move that XPC service into the directory that we saw earlier, Contents/XPCServices.
In the project itself I have three groups of sources. The first is a set of shared sources. You'll find that model objects and interfaces described in headers are very common to share between both your application and a service.
We also have the Flight Finder sources themselves and the TicketAgentService.
We started with interfaces, let's look at that. Here is the protocol for the Agent with buyTicket:onDate:maxCost:reply:.
In the application delegate, for this demonstration I've chosen to create the connection in the applicationDidFinishLaunching: method.
Here I create the connection; you see I'm using the bundle identifier for my TicketAgentService. I set its remoteObjectInterface to use the Agent protocol.
And I resume the connection.
I'm storing my remote object proxy as an ivar in this object, and I create it and here is the error handler, where again on the main queue I'm going to update the UI to show that something went wrong.
For the listener, in the main function of the TicketAgentService, this is where I create my service listener, set up its delegate, and resume the connection.
The TicketAgent object implements the listener:shouldAcceptNewConnection: delegate method. Here I set up the exportedInterface, the exportedObject, and resume the connection.
Now when the application chooses to ask the ticket agent for something, we just send our buyTicket:onDate:maxCost:reply: message to the agent, and when the reply comes back we're going to execute this on the main queue, which updated the UI as we saw earlier.
And in the TicketAgent, that implementation is just right here, where we create our ticket, make sure that nobody is trying to get us a really cheap ticket, so we make sure it's over $100, pretend that we're actually doing some real work by sleeping, and then reply with the ticket value.
So I want to show you how easy it is to add new functionality to this as your requirements change. So as you saw, we have this Today's Special button which is doing nothing, so I'm going to go ahead and implement that.
We're going to start again with interfaces. I'm going to add a new method to our protocol, it's called getSpecialFlight:. You see it takes no arguments besides the reply block, and when the reply comes back we'll have a Ticket.
Then in the TicketAgent, we're going to implement that new method, because we added it to our protocol.
I'm just going to, like we saw on the slides, create our new ticket, set its destination and price to New York City and $499, and then reply with the return value, and in the application we need to just hook up our button, and here it is, it was just empty before, and I'm sending a message to my remote object proxy, getSpecialFlight:, and on the main queue when the reply comes back we set up some string values in the UI.
So let's go ahead and re-run this, and make sure that it works. I'll bring up Activity Monitor again so we can see if our process starts.
I click Today's Special, and you see there our TicketAgent service was started on demand when we asked for a value, and we got back our reply of $499.
Alright. Now we're going to talk about how those objects are actually moving between processes. That's the heart of the matter, the most important feature of NSXPCConnection, and that's using coding.
So if you've used KeyedArchiver, then you're familiar with NSCoding already. If not, pay attention, because I'm going to do a quick review.
NSCoding is made up of two parts: there's the NSCoder subclass like NSKeyedArchiver or NSKeyedUnarchiver, and the NSCoding-conforming class.
This is made up of two activities as well, there's encoding in which the coder takes an in-memory graph of NSCoding objects and turns them into an archive that is suitable for storage on disk or for sending between processes maybe, and then there's decoding, where we take that archive and convert it back into an in-memory representation of those objects.
So here's encoding. You see I've got a graph of objects, an itinerary that holds a ticket that has a string.
So the most important method in encoding is called encodeWithCoder:, and in there the job of this method is to tell the coder what data is needed to be stored so that we can effectively restore ourselves later, using methods like encodeBool:forKey: and encodeObject:forKey:.
So when I call encodeObject:forKey: of course the coder will then call encodeWithCoder: on the ticket. And there we're going to encode the string object.
Now you don't need to worry about how NSString encodes itself, because as long as NSString conforms with NSCoding, we know it can be included in the archive.
On the flip side is decoding. So here the most important method is initWithCoder:. Here the job of this method is to get back that data from the coder, and initialize the object, using methods like decodeBoolForKey: and decodeObjectForKey:.
And of course when we called decodeObjectForKey: we're going to alloc and initWithCoder: on the Ticket object, which will decode the string object, and you don't need to worry about a string inits itself, as long as it conforms with NSCoding, we know it can be included in the archive.
So NSXPCConnection uses the exact same NSCoding design pattern. On message send, all the argument values are encoded, and then on the message receive, all the arguments are decoded.
And NSXPCConnection uses a new NSCoder subclass, it's not NSKeyedArchiver, but it does implement keyed coding.
So here's the method that you might be sick of by now, buyTicket:onDate:maxCost:reply:, so let's use it as an example.
When this message is sent, the coder will do something like encodeObject:forKey: argument 1 and argument 2, because those are objects, and encodeInteger:forKey: argument 3, because it's an integer.
As I mentioned earlier, the reply block is held onto, until the reply comes back, so if you capture a lot of memory in your reply block, just be aware that we have to keep that alive until the reply comes back.
And then on receive we decode those objects using decodeObjectForKey: and decodeIntegerForKey:.
Then we create that reply block proxy and stuff 'em all into the arguments to that message that you've implemented on your exported object.
Now you may be wondering how the coder knows what kind of object to alloc here, because it's not specified in the method, and the return type is just id.
The answer is that the class came from the archive.
However, the archive came from the remote process, and because we're talking about privilege and process boundaries, we shouldn't just trust the remote process's opinion about what kind of class should be allocated.
So we need some way to specify the expected class. The reason is because that class could have been any kind of class in your application, or any frameworks or libraries that is uses, and that's a huge surface area to find a security vulnerability in.
It would be much better to just specify up front a very limited set of classes that this object can be.
So here's one way we can do it. In the protocol that we saw earlier, we specified some class information.
We said that the first argument is a string, the second one is a date, the first argument of the reply block is a Ticket.
This information we can actually get now, using some new metadata provided by the clang compiler in Mountain Lion. So— (applause) Oh, good.
So the use of the clang compiler is required for this feature, so that's important to note.
But as we saw earlier, objects don't travel by themselves, they come in graphs.
So we need some more information about this object beyond just the top level that was specified in the protocol.
And to do that we have a new protocol called NSSecureCoding, and three new NSCoder methods.
In the past, in your initWithCoder: you may have done something like this: decodeObjectForKey:, and then check the result to see if it's a kind of Ticket class or a subclass of Ticket.
Now this is useful from some points of view, if we accidentally send that ticket object a message that it doesn't implement, we would prevent an exception in this case, but in terms of security it's already too late.
As I mentioned that's because we already called alloc, and we already called initWithCoder:. So it's not good enough.
Instead, what we need to do is specify it up front, using these new NSCoder methods.
The first is called decodeObjectOfClass:forKey:, and so if the expected object is of one kind of class or a subclass, then you can use this method.
Now what'll happen when using NSXPCConnection is if the class doesn't match, then we're just going to drop the message on the floor.
Your application or service doesn't need to worry about it, doesn't even have to know that something came in that was unexpected.
Under the hood what's happening is that the coder throws an exception, and NSXPCConnection catches it.
So if you want to, you can catch the exception yourself and take some kind of alternate behavior.
Now if the class is not just one kind of class, but a property list type, one of our property list types, then you can use this method, decodePropertyListForKey:.
And if it's one of several kinds of classes that aren't property list types, or it's a collection, then you can use this method, decodeObjectOfClasses:forKey:.
The reason that we use this for collections is because a collection by design doesn't know a lot of details about the content that it holds.
So we need just a little bit of extra help from you to tell us what kind of objects are expected to be in it.
Here's how you do it. Create a set that contains the expected classes, like that method looked like it contained an array of Tickets, so I have [Ticket class] and [Array class], and then I use decodeObjectOfClasses:forKey:.
So in order to send your objects over NSXPCConnection, they are required to adopt NSSecureCoding. Oh by the way, NSSecureCoding is a subprotocol of NSCoding.
So here I've got my ticket object and you see it's got one property, a string called destination.
In its implementation, the encodeWithCoder: looks exactly the same as it has been in the past, where I use encodeObject:forKey:.
Where things look a little bit different is in the initWithCoder:.
There you're going to use those new methods I just talked about, decodeObjectOfClass:forKey:.
Now there's one more method to implement, this is the method in NSSecureCoding, called supportsSecureCoding.
It's a class method, and what you need to do is override it and return YES.
There's one main reason for this. NSSecureCoding protects a very specific security issue, that arbitrary code execution exploit that I mentioned.
What it doesn't do is protect you against things like buffer overruns or a false trust in the remote process.
By that I mean, imagine that we take a string argument, and then we just take its contents and run it as a Perl script as root.
Well, clearly that's going to be an insecure kind of—the whole nature of it is insecure. We can't protect against that.
What we want you to do is, when you adopt NSSecureCoding in your class, review the code in your initWithCoder:, put on your security hat, and look for these kinds of security vulnerabilities.
Or, you know, maybe the class isn't suitable for crossing a privilege boundary at all.
So put on your hat, review the code, and then implement that method right next to your initWithCoder: and return YES.
And to help you with this, we implemented a rule: that is that if you override initWithCoder: in your class, you must also override supportsSecureCoding and return YES.
So if your superclass implements NSSecureCoding and you override initWithCoder:, your superclass's implementation of supportsSecureCoding isn't enough.
We want you to look at your initWithCoder:, and make sure that it's secure, and to indicate that you just implement that one method and return YES.
So we talked a little bit about collections earlier. Here we have a top-level collection, that's one in the protocol.
Again, the collection doesn't know any details about the content, and because the protocol just says array, we can't know from the protocol.
So again we're gonna need a little help to understand what kind of objects we expect to be in here.
If it's a property list type, one of these, we're going to automatically whitelist it for you and you have no additional work to do.
If, on the other hand, it's one of your objects, like this one appears to be an array of Ticket objects, then we need to set that information up in the NSXPCInterface.
Here's how you do it. There at the top is our protocol again, so we're going to create our interface, create a set of expected classes—in this case, I already know the top-level is an array, so you just need to specify the stuff that's in it, Tickets—and then use this method on the interface object.
setClasses:forSelector:argumentIndex:ofReply:. I'm going to go through it argument by argument.
The first one is the set of expected classes.
The second is the selector in the protocol that we're modifying.
The third argument is the index of the argument that we're modifying, here it's argument index 0.
And the last argument in this case will be NO, because this method doesn't have a reply block, we're modifying the argument to the method itself.
If instead it has a reply block like this one, getLotsOfTickets:, it's a getter, the selector's going to be that method, the last argument is YES, because we're talking about the reply block, not the method itself, and then the argument index refers to the argument of the reply block.
So again, use this method to tell the interface what the contents of your top-level collections are.
Next let's talk about a few design patterns that you'll see as you start to adopt NSXPCConnection.
In your service there are two key patterns. The first is one of having little state, mostly functional and short-lived kind of state.
In that case it's very common to have one singleton thread-safe object that implements both the NSXPCListenerDelegate protocol and your exported object protocol.
The other kind of pattern is one where you have lots of state, and you have long-lived kind of behavior.
In that case you'll typically have one NSXPCListenerDelegate, and it in its delegate method will create one new exported object per connection.
In the application you'll see a pattern of, as we saw a little bit earlier, asynchronous UI updates, and further separation of interface and implementation.
So I'm going to do one more demo. I want to show you how to add an XPC service to a project.
We're going to move some code from the main application to that service, and I'm going to show you briefly how you might debug it.
So let's go ahead and quit the Flight Finder, we don't need that.
Here I want to use a slightly more complicated example, it's called SandboxedFetch.
Now this is a public sample code that I've actually taken and modified to use NSXPCConnection.
What it does is, you just give it a URL, and it will download that from the internet and then if this box is checked it will compress that file before saving it to a user-specified location.
So here I've picked a URL that has a fairly large image so that we can see some progress, and you see we've been presented with our save panel, so I'm just going to go ahead and save it on the desktop, and there's our file.
So let's look at the implementation of this project.
As you can see here, we just have one target, so this is a single-process application. It's code signed, and it has the entitlements to allow it to have an outgoing network connection, and also read-write access to my user-selected file.
If your application deals with file formats, especially compression formats, image formats, movie formats, audio formats, those kinds of things, those are notorious for having security issues and problems that cause crashes.
So those are prime candidates for moving into a separate helper process that has a limited set of permissions, so that's what we're going to do.
We're going to move the compression stuff into an XPC service.
To do that I'm going to add a new target.
Here in the Framework & Library section you see there's our XPC Service template. You should give your XPC service a name that reflects what it's going to do, like zip-service, and I also suggest that you make the company identifier have your application name so that the full bundle identifier is a qualified bundle identifier, so it's easy to identify.
Again, this bundle identifier is what we're going to use to connect to it.
Go ahead and click Finish.
We're going to add a file to this zip service, a property list type called zip-service.entitlements, and the purpose of this file is to enable sandboxing in our XPC service like our main application has.
The easiest way to do this is we're just going to crib something from the main application here.
Here's the same set of entitlements we saw in that GUI earlier, but this is the real source of them.
So I copy the app-sandbox entitlement and I'm just going to paste it here.
So we're going to have no permissions for this helper process besides just enabling the sandbox.
Go back to our configuration; in the Build Phases I'm going to add a dependency on the library called libz, that's what's actually doing the compression.
And in the Build Settings I'm going to turn on sandboxing by code signing my application.
Gonna drag it over from here so I don't make any typos on stage. There we go.
And we're done configuring our zip service. Now we just need to configure our application.
This is in the same way that we saw earlier, we're going to add a dependency from the SandboxedFetch application on the new service that we just created. That ensure it's built correctly and up to date when we need it to be.
And add a new Copy Files build phase, and into the wrapper we're going to put it in Contents/XPCServices, the same path that we saw earlier, and the file we're going to put there is our XPC build product.
Let's just go ahead and build this application right now to see if everything worked correctly, so no build errors.
And I'm going to show this build product in Finder. And there's our XPC service in our XPCServices directory, so it's been put in the right place.
Okay, so that was step 1. Now we're going to move some code from the main application into this helper service.
Here is the header file that describes the zip interface. It's a singleton object where you get the sharedZipper and you send it this message compressFile:toFile:withReply:. It uses NSFileHandle.
This is a good time to mention that NSFileHandle has been enhanced in Mountain Lion to allow it to be sent between processes using this NSXPCConnection.
So that means that, as you notice, we didn't give the helper service any permission to open files on its own; the main application has it.
And when we send this file to it over NSXPCConnection, the other process will have permission to access it automatically.
If you remember, one of my patterns was a further separation of interface and implementation, so what we're going to do is split out this compressFile logic into a new interface called Zip.
And this Zipper object will conform to that, and it will also be a singleton object because it's got very little state to hold, and it's short-lived, so we're going to implement both the exported object protocol and the NSXPCListenerDelegate protocol.
Let's go ahead and do that now.
Here is the implementation of that Zipper functionality; this is the compressFile method.
What I want to do here is put in the implementation of the NSXPCListenerDelegate protocol.
listener:shouldAcceptNewConnection:, and again we're going to set our exported interface to be the Zip protocol, set our exported object to be self, because it's a singleton, and resume the connection.
Now pay attention because this is the hardest part.
I'm going to move this file from SandboxedFetch to the zip-service. There we go.
(whistle) Okay. So that was the interface.
Next let's look at the listener.
So we can add an #import of our header there, the Zipper functionality that we just moved into this service.
We can go ahead and delete all of that boilerplate, and put in our implementation of the NSXPCListener.
Again we're going to get the serviceListener, set its delegate to be one of these Zipper objects, and resume the connection.
So finally we just need to, in the main application, instead of creating the zipper itself, we're just going to instead send that request to our XPC service.
So this method is called saveFile:. And its job is to present that UI where we displayed the panel, and then also, once the user has decided to compress it, create a file handle, and then zip the file.
What we'll do here is: create the connection—these are the exact same lines of code we saw earlier, initWithServiceName:, there is our com.demo.SandboxedFetch.zip-service bundle identifier—set the remoteObjectInterface to be the Zip protocol, and resume the connection.
Now as I mentioned earlier, we need to invalidate the connection when we're done with it. This reply block will be called after the compression is done.
So that seems like a good time to invalidate the connection, because we're no longer going to need it after that point.
And finally, instead of using the singleton SharedZipper that was in-process, we're going to use the remote object proxy that comes from the connection, that implements the Zip protocol.
So let's run it again and see if everything behaves correctly.
I'll bring up Activity Monitor again, and we're going to filter it for zip-service.
So I'll click Fetch, the file is downloaded, I click Save, replace the one that we already downloaded, and you see that as the compression started to happen, our zip-service started and it's correctly sandboxed.
I mentioned earlier that NSXPCConnection has a feature of providing crash protection.
To demonstrate that I'm just going to go ahead and kill this background process.
And you see that the main application just continued to run, in fact the user didn't even need to know that that process went away.
So I'll take this opportunity to show you some debugging tips about how to actually go ahead and debug these XPC services.
There's a couple of ways.
One way is you notice that we have a new zip-service here, and we can edit this scheme and choose our new executable, in the same place that we saw earlier, and make sure that you choose Wait, because we don't want to start it ourselves, it's started on demand for us.
I'm going to click OK, and then I'm just going to run, and you see that Xcode is waiting for that service to start.
Now if I go back to my SandboxedFetch and we go ahead and download this file again, you notice that—oh, maybe a good idea to actually set a breakpoint in the compressFile:.
Okay. Kill it....
Through all that our main application is still running, so that's the crash protection for you.
I'm going to go ahead and re-run our zip service, and it's waiting with a breakpoint this time.
Download and save, and the main application is just waiting for the reply here, you notice.
Asynchronously, because I can still, you know, although it's displaying a sheet, I can still— wow, that's interesting.
(laughter) Interesting bug. Okay.
Somebody report that one.
Anyway, so in Xcode, we've stopped on our breakpoint in the SandboxedFetch helper process.
And you see we're allowed to both debug the service and the application at the same time.
So here I can view the values of various values or step through my process, and then when I continue it, our main application has received its response, and hidden the panel and everything is finished.
So it's actually pretty easy to split up your process and debug it at the same time in Xcode.
Okay, let's go back to our slides.
So, just like we moved from single-threaded to multi-threaded applications, today we're moving from single-process to multi-process applications.
It provides two key benefits.
The first is security, by allowing these helper processes to have only the permissions that they need to do their job.
The second is crash protection, so if something goes wrong, the user doesn't have to be interrupted and lose all of their work.
NSXPCConnection is designed to help you connect them. Again, it works with your own objects and interfaces.
And it's designed to be both secure and modern.
There are three main pieces: the interfaces, which provide a clear division of responsibility and abstract away implementation details; connections, which provide the bidirectional communication channel between them; and finally listeners, whose job it is to await new incoming connections, and allow a delegate to configure them.
For more information, here are some links for you.
The SandboxedFetch2 example that I showed is available as part of this session.
In the sample that you see online, I've split it up into three processes, because three is better than two, and the third process actually does the downloading, so we've even separated out the permissions further.
And for those of you watching on video, we hope to get this promoted to a public sample code as well, soon.
NSXPCConnection is based on top of XPC of course, so you'll find a lot of helpful information, especially about the error handling stuff, in the XPC man pages.
We have one more related session, that's tomorrow morning, and that's Asynchronous Design Patterns with Blocks, GCD, and XPC.
This session is about the underlying parts of NSXPCConnection, but the design patterns they're going to go through are really helpful to help understand how you might segregate your application.
I gave you some simple examples, and they'll show you some more advanced ones.
So that's it, I hope you go out and make some great applications.