Imagine if all the users in a family had our recipe application and wanted to be able to see everyone else’s recipes. You can probably come up with many such scenarios for sharing data across a local area network. If your application sits on a user’s desktop and laptop, there is a fair chance the user wants to keep that data in sync. Of course, this can be done with iCloud for a single user, but imagine a small office environment or family of computers. Not every user has the same iCloud account, and you may not want to share the entire data set to every user. Being able to set up a local area sharing can solve the need to share partial or complete data in a local environment.
Core Data is generally considered to be a single-user/single-application per-sistent store. However, as we explored in Chapter 6, Using iCloud, on page 99, Core Data can be used beyond the single-user/single-application design with iCloud along more than one application and/or device to access the same data. In this chapter, we are going to explore using Core Data with distributed objects. Distributed objects enable a Cocoa application to call an object in a different Cocoa application (or a different thread in the same application). The applications can even be running on different computers on a network.
To take this idea one step further, we are going to add Bonjour into the design.
Bonjour, also known as zero-configuration networking, enables automatic discovery of computers, devices, and services on IP networks. With this combination, we can provide access to a Core Data repository to any client on the network “automatically”—without user interaction.
Before we go into the details, let’s examine the cons for this design.
• Scalability: This design does not scale well at all. When we are working with a couple of clients on a network, the design performs just fine. But
when we start scaling it to half a dozen or more clients, it starts to slow down very quickly. There are optimizations that we can do, but if you have more than a couple of clients, it’s better to use a full database solu-tion instead of Core Data.
• Threading: Although all the calls to the server are performed on the main thread, calls within objects passed by reference are not by their very nature. Therefore, if we pass an NSManagedObject by reference to a client and that client makes a change to the NSManagedObject, we are in a worst-case situation with regard to threading.
11.1 Building the Server
In a normal client-server application, the server would be a background or GUI-less application. In this demonstration, we are going to start with a normal single persistent store Cocoa application instead. There is no benefit to having a UI for a server in a production environment, but for testing, it is useful to see the activity on the server. Therefore, we start with creating a Core Data Cocoa application called DistributedCDServer. The user interface for the server is a single window with a table view displaying the list of items in the Core Data persistent store, as shown here:
The data model for this example is composed of two entities. The top-level entity is named Test and has two properties: name and children. The second entity is called Child and also has two properties: name and parent. The two Chapter 11. Distributed Core Data
•
190objects share a many-to-one relationship between the properties children and parent. The resulting model looks like this:
Distributed Objects Protocol
When I am working with distributed objects, I prefer to contain the contract between the client and the server within a protocol. For this application, we are going to have a few methods that the clients can use to query the server, but we are not going to have any server to client queries.
The resulting protocol is as follows:
DistributedCDServer/PPDistributedProtocol.h
#define kDomainName @"local."
#define kServiceName @"_pragProgExample._tcp"
@protocol PPDistributedProtocol - (oneway void)ping;
- (byref NSManagedObject*)createObject;
- (byref NSManagedObject*)createChildForObject:(byref NSManagedObject*)parent;
- (oneway void)deleteObject:(byref NSManagedObject*)object;
- (byref NSArray*)allObjects;
- (byref NSArray*)objectsOfName:(bycopy NSString*)name
withPredicate:(bycopy NSPredicate*)predicate;
@end
When we are working with distributed objects, we need to define how nonscalar attributes are handled. (These are discussed in depth in Apple’s documenta-tion.) In our protocol, we are passing most of the objects byref, which means an NSDistantObject is created on the receiver as a proxy to the object residing on the server. This is different from bycopy, which makes a copy of the object on the receiving end. One of the interesting differences between these is that KVO works across a distributed object when it is passed byref. This will be demonstrated as we build the application.
Broadcasting the Service
Distributed objects work by using good old Unix sockets. Fortunately, these are wrapped with NSSocketPort for us, so we do not need to use the raw C functions and all the complexity that entails. To use sockets, we need to know the address and port of the socket to talk to. This can be entered by the user, which is a suboptimal experience, or we can discover it using Bonjour. To use Bonjour, we must set up a broadcast on the server for the client to
In the -startBroadcasting method, we first initialize a new NSSocketPort. When we use the default -init method, the NSSocketPort chooses a random open port for us to use. However, we need to broadcast this port information as part of the Bonjour service. Therefore, we need to extract the port information from the NSSocketPort object. In a production environment, we probably want to define a port to use instead of selecting one at random.
DistributedCDServer/AppDelegate.m
- (int)portFromSocket:(NSSocketPort*)socket {
struct sockaddr *address = (struct sockaddr*)[[receiveSocket address] bytes];
uint16_t port;
if (address->sa_family == AF_INET) {
port = ntohs(((struct sockaddr_in*)address)->sin_port);
} else if (address->sa_family == AF_INET6) {
port = ntohs(((struct sockaddr_in6*)address)->sin6_port);
Chapter 11. Distributed Core Data