Skip to content

Core Data and Sync Services

2008 July 26

Apple’s Sync Services framework allows applications and devices to keep in sync with each other’s data. If you subscribe to the MobileMe service, for example, it is Sync Services that handles synchronization between the server, your computers and any connected devices such as an iPhone. This article discusses adding synchronization capabilities to Core Data applications. We’ll start by looking at the information you must provide to allow synchronization to take place and then build two sample applications that will keep in sync with each other.

The two applications running showing the same data displayed in different ways
Each window is from one application, and each keeps its own copy of the data; any change made and saved to one will be communicated to the other.

Sample project downloads are available at the end of this article.

The Synchonizing Parts

For an application to be able to synchronize via Sync Services, it needs to provide various bits of information. In the first place, there needs to be a registered Sync Schema that defines the structure of the data available to synchronize. Secondly, each application needs to provide and register a Client Description that defines which of the available data in the sync schema the application will use. Then, of course, there’s the necessary code to handle the synchronization itself.

Synchronization with Core Data

The Sync Schema lists the model objects available to be synchronized. Apple’s Bookmarks schema, for example, lists two entities — the Bookmark entity and the Folder entity, each with attributes for names, urls and parent relationships etc.

Traditionally, a sync schema is created from scratch with entries for the different entities and their attributes, entries for relationships and inverse relationships between entities, entries to allow different types of synchronization, and a whole lot more. Writing such a schema can be a bit daunting; luckily, creating a schema under Core Data is a great deal easier. Provided you define your data model with the Data Modeling Tool in Xcode, you can have most of your schema property list created for you automatically without the need to hand-code all the entities, attributes and relationships.

The Client Description, on the other hand, won’t be written for you, but this is a relatively straightforward property list simply telling Sync Services which entities and attributes your application wishes to synchronize.

Writing code to handle the synchronization typically includes methods that handle pushing and pulling data. You must invoke various methods to start the synchronization process, register your sync schema and client description, deal with changes made in your application and inform Sync Services about these changes, and then handle any changes made by other applications. When using Core Data to handle your underlying data, most of this is handled for you in the NSPersistentStoreCoordinator.

Demo Application 1

Our first project is a version of the ubiquitous Departments and Employees application, this time using a non-document-based application. We’ll start by building a standard Cocoa Core Data application using Xcode. Only after the application is working will we then start adding synchronizing functionality — this way, it’s obvious how easy it is to add synchronizing capabilities to existing applications.

The Basic Project

  • Begin by creating a Cocoa Core Data Application (don’t use the Document-based template) and call it ‘DepartmentsAndEmployeesSyncApp’.
  • In the Xcode Data Modeler, create a ‘Department’ entity with a ‘name’ string attribute. Create a ‘Person’ entity with ‘firstName’ and ‘lastName’ string attributes, and a ‘salary’ decimal attribute. In the Department entity create an ‘employees’ to-many relationship to the Person entity, and create its inverse ‘department’ to-one relationship between Person and Department.
  • Open MainMenu.nib in Interface Builder and create an NSArrayController called ‘Departments’. Set its Mode to ‘Entity’ and Entity Name to ‘Department’. Check the ‘Prepares Content’ box. Bind its Managed Object Context to the App Delegate object’s ‘managedObjectContext’.
  • Create a second NSArrayController called ‘People’ for the Person entity that also Prepares Content. Bind its Managed Object Context to the App Delegate’s managedObjectContext and its Content Set to the Department controller’s ‘selection’ ‘employees’.
  • For the user interface, add to the nib file’s Window layout a one-column NSTableView that displays the departments, binding its column Value to the Departments array controller ‘arrangedObjects’ ‘name’ key.
  • Add a second NSTableView, this time with three columns, bound to the People array controller ‘firstName’, ‘lastName’ and ‘salary’ keys respectively. Drag an NSNumberFormatter onto the salary column.
  • Finally add ‘+’ and ‘-‘ buttons for both table views that connect to the relevant add: and remove: array controller actions.
  • Build and run your application to make sure everything works as it should. Mine looks like this:
    The first application in use

Nothing particularly impressive here — just the construction of the basic project ready for synchronization implementation. Obviously, we’re using a standard Core Data application rather than a document-based app so that we have only a single known set of data when we come to start synching. Now we’ll move on to define the synch schema.

The Sync Schema

  • In Xcode, create another new project. Use the ‘Sync Schema’ template, which for me is listed under ‘Standard Apple Plug-ins’. Call this project ‘DemoEmploymentData’.
  • The resultant project contains various files, the most important of which is the ‘Schema.plist’ file. Open this file and you’ll find a demo schema already in there. We can remove most of this as our Entities information will be generated for us from our data model in the DepartmentsAndEmployeesSyncApp project.
  • Delete from the opening Entities key down to the closing of the array for that key. You should be left with a DataClasses key and a Name key.
  • Under the DataClasses key we define names for the collections of data we are going to be sharing. Since we’re only sharing one type of information (employment data) we will define a single data class. Replace the existing ‘com.mycompany.myschema.dataclass’ string with ‘com.mycompany.DemoEmploymentData’. Sync Services uses reverse DNS syntax to keep track of its information. Note that this key has to be unique across all schemas.
  • There is another key under DataClasses called ‘ImagePath’. This might be called ‘Image’ in your schema; if it is, rename it to ‘ImagePath’ to avoid errors later. This path is used to display an icon representing the data class; this might be used, for example, if it’s displayed in the MobileMe System Preferences tab. For this demonstration, we’ll leave it as it is with the standard ‘SyncDataClass.tiff’ image.
  • Change the string for the ‘Name’ key to be ‘com.mycompany.DemoEmploymentDataSchema’.
  • To make use of the automatic generation functionality, define a new key called ‘ManagedObjectModels’ with an array containing a string pointing to our data model. This is a relative path; assuming you’re going to follow this tutorial through, enter it as:
    <key>ManagedObjectModels</key>
    <array>
    	<string>../../../DepartmentsAndEmployeesSyncApp_DataModel.mom</string>
    </array>
  • If you wish, open the Schema.strings file and enter localization information for the schema keys.

The complete schema.plist file should look something like this:

<?xml version="1.0" encoding="ASCII"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 
<!-- Schema.plist -->
<!-- EmployeeData -->
<!-- Created by Tim Isted on 25/07/2008. -->
<!-- Copyright __MyCompanyName__ 2008. All rights reserved. -->
 
<plist version="1.0">
<dict>
	<key>DataClasses</key>
	<array>
		<dict>
			<key>Name</key>
			<string>com.mycompany.DemoEmploymentData</string>
			<key>ImagePath</key>
			<string>SyncDataClass.tiff</string>
		</dict>
	</array>
 
	<key>ManagedObjectModels</key>
	<array>
		<string>../../../DepartmentsAndEmployeesSyncApp_DataModel.mom</string>
	</array>
 
	<key>Name</key>
	<string>com.mycompany.DemoEmploymentDataSchema</string>
</dict>
</plist>

Now we need to include the built schema in our original app project.

  • Build the DemoEmploymentData project.
  • Return to the DepartmentsAndEmployeeSyncApp. Right-click on the ‘Resources’ group and choose ‘Add -> Existing Files…’.
  • Find the DemoEmploymentData project directory and inside that find the ‘build’ directory. Inside there you’ll find a ‘release’ directory containing a directory called ‘DemoEmploymentData.syncschema’. Select this directory and Add it to the project. In the dialog box that appears after this, check the ‘Create Folder References for any added folders’ radio button so that the directory structure is maintained. Once the syncschema is added, your Xcode project’s Groups & Files outline view should look like this:

    The Xcode Project Listing showing the syncschema listed

Changes to the Data Model

Now that we’ve defined our data schema by telling it about our data model, we need to make a few changes to the data model so that it will populate the schema properly.

  • Open up the data model in Xcode. Click on the Department entity and in the far top right of the window, click the little ‘sync’ icon to setup the synchronization settings for the entity. Enter the Data Class as the one we setup earlier in the syncschema ‘com.mycompany.DemoEmploymentData’. Make sure the ‘Synchronize…’ box is ticked as shown:
    The entity synchronization tab in the Xcode data modeler
  • Click the ‘name’ attribute and check the ‘Identity property’ box. This specifies that the name attribute will be unique and used to track different departments.
  • For the person entity, make sure the data class is again ‘com.mycompany.DemoEmploymentData’ and that it should be synchronized. Specify that the ‘firstName’ and ‘lastName’ attributes are Identity properties.

The Client Description

Our final piece of synchronization information is the client description that defines which data in the syncschema we will be synchronizing.

  • Right-click on the ‘Resources’ group in the project listing and choose ‘Add -> New File…’. Choose the ‘Sync Client Description’ under ‘Sync Services’ and call the file ‘clientDescription.plist’.
  • Under the Entities key, change the first dictionary key to ‘com.mycompany.DemoEmploymentData.Department’.
  • The first entry in the following array is a string that must be left as ‘com.apple.syncservices.RecordEntityName’. The subsequent strings define the chosen attributes for the specific entity; change the ‘first name’ string to be just ‘name’. Add another string for the ‘employees’ relationship.
  • Add a second entity key called ‘com.mycompany.DemoEmploymentData.Person’ and again add the ‘com.apple.syncservices.RecordEntityName’ string. Also add strings for the ‘firstName’, ‘lastName’, ‘salary’ and ‘department’ attributes.

Your finished client description should look something like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 
<!-- clientDescription.plist -->
<!-- DepartmentsAndEmployeesSyncApp -->
<!-- Created by Tim Isted on 25/07/2008. -->
<!-- Copyright 2008 __MyCompanyName__. All rights reserved. -->
 
<plist version="1.0">
<dict>
	<key>DisplayName</key>
	<string>DepartmentsAndEmployeesSyncApp</string>
 
	<key>Entities</key>
	<dict>
		<key>com.mycompany.DemoEmploymentData.Department</key>
		<array>
			<string>com.apple.syncservices.RecordEntityName</string>
			<string>name</string>
			<string>employees</string>
		</array>
 
		<key>com.mycompany.DemoEmploymentData.Person</key>
		<array>
			<string>com.apple.syncservices.RecordEntityName</string>
			<string>firstName</string>
			<string>lastName</string>
			<string>salary</string>
			<string>department</string>
		</array>
	</dict>
 
	<key>Type</key>
	<string>app</string>
</dict>
</plist>

Making our application synchronize

In order to get our application to synchronize its data, we need to make a few modifications to the way the application sets up its persistent store. We also need to change the save action so that whenever data is persisted, it is also synchronized.

  • Firstly, we need to add the relevant framework to the project. Expand the Frameworks group in your project browser and right-click on the Linked Frameworks group. Choose ‘Add -> Existing Frameworks…’ and select the ‘SyncServices.framework’.
  • Next we’ll modify the application delegate so that it handles both synchronization and the necessary callbacks. Open ‘DepartmentsAndEmployeesSyncApp_AppDelegate.h’. After the #import for Cocoa.h add a line to import the SyncServices header:
    #import <syncServices/SyncServices.h>
  • In order to be able to handle the synchronization callbacks, an object must conform to the NSPersistentStoreCoordinatorSyncing protocol. So, change the object interface to be:
    @interface DepartmentsAndEmployeesSyncApp_AppDelegate : NSObject <nspersistentStoreCoordinatorSyncing>
  • We’re going to be writing a method that will initiate a sync so add a declaration for it:
    - (void)syncAction:(id)sender;

The Core Data framework handles synchronization through the NSPersistentStoreCoordinator. To support fast synchronization operations, you need to specify a ‘fastsyncstore’ file for the coordinator to maintain.

  • Open the DepartmentsAndEmployeesSyncApp_AppDelegate.m files and find the persistentStoreCoordinator: method. We need to change this so that it keeps track of the created NSPersistentStore, and then adds the .fastsyncstore file to it. Rewrite the method so it looks like this:
    - (NSPersistentStoreCoordinator *) persistentStoreCoordinator
    {
    	if (persistentStoreCoordinator != nil) {
    		return persistentStoreCoordinator;
    	}
     
    	NSFileManager *fileManager;
    	NSString *applicationSupportFolder = nil;
    	NSURL *url;
    	NSError *error;
     
    	fileManager = [NSFileManager defaultManager];
    	applicationSupportFolder = [self applicationSupportFolder];
    	if ( ![fileManager fileExistsAtPath:applicationSupportFolder isDirectory:NULL] ) {
    		[fileManager createDirectoryAtPath:applicationSupportFolder attributes:nil];
    	}
     
    	url = [NSURL fileURLWithPath: [applicationSupportFolder stringByAppendingPathComponent: @"DepartmentsAndEmployeesSyncApp.xml"]];
    	persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
     
    	NSPersistentStore *store = [persistentStoreCoordinator addPersistentStoreWithType:NSXMLStoreType configuration:nil URL:url options:nil error:&error];
     
    	if( store != nil ) {
    		NSURL *fastSyncFileURL = [NSURL fileURLWithPath:[applicationSupportFolder stringByAppendingPathComponent:@"DepartmentsAndEmployeesSyncApp.fastsyncstore"]];
    		[persistentStoreCoordinator setStoresFastSyncDetailsAtURL:fastSyncFileURL forPersistentStore:store];
    	}
    	else {
    		[[NSApplication sharedApplication] presentError:error];
    	}
     
    	return persistentStoreCoordinator;
    }
  • We need to write a method that will return an ISyncClient object ready for use in synchronization. Sync Services keeps track of different sync clients by using the unique application bundle identifier. Add the following method at the end of the file. It asks whether such a client exists for our application; if not it creates one, registers the schema and client description before specifying which client types it should synchronize with, displaying an error to the user if something goes wrong:
    - (ISyncClient *)syncClient
    {
    	NSString *clientIdentifier = [[NSBundle mainBundle] bundleIdentifier];
    	NSString *reason = @"unknown error";
    	ISyncClient *client;
     
    	@try {
    		client = [[ISyncManager sharedManager] clientWithIdentifier:clientIdentifier];
    		if( client == nil )
    		{
    			if (![[ISyncManager sharedManager] registerSchemaWithBundlePath:[[NSBundle mainBundle] pathForResource:@"DemoEmploymentData" ofType:@"syncschema"]])
    			{
    				reason = @"error registering the DemoEmploymentData sync schema";
    			}
    			else
    			{
    				client = [[ISyncManager sharedManager] registerClientWithIdentifier:clientIdentifier descriptionFilePath:[[NSBundle mainBundle] pathForResource:@"clientDescription" ofType:@"plist"]];
    				[client setShouldSynchronize:YES withClientsOfType:ISyncClientTypeApplication];
    				[client setShouldSynchronize:YES withClientsOfType:ISyncClientTypeDevice];
    				[client setShouldSynchronize:YES withClientsOfType:ISyncClientTypeServer];
    				[client setShouldSynchronize:YES withClientsOfType:ISyncClientTypePeer];
    			}
    		}
    	}
    	@catch (id exception) {
    		client = nil;
    		reason = [exception reason];
    	}
     
    	if( client == nil ) {
    		NSRunAlertPanel(@"You can not sync your Employee Data.", [NSString stringWithFormat:@"Failed to register the sync client: %@", reason], @"OK", nil, nil);
    	}
     
    	return client;
    }
  • Next, implement the syncAction: method to start off the synchronization process by calling the syncWithClient: method on the application’s persistentStoreCoordinator:
    - (void)syncAction:(id)sender
    {
    	NSError *error = nil;
    	ISyncClient *client = [self syncClient];
    	if( client != nil )
    	{
    		[[[self managedObjectContext] persistentStoreCoordinator] syncWithClient:client inBackground:NO handler:self error:&error];
    	}
     
    	if( error != nil )
    	{
    		[[NSApplication sharedApplication] presentError:error];
    	}
    }
  • Find the saveAction: method and change it to call our syncAction: method once the managedObjectContext has saved:
    - (IBAction) saveAction:(id)sender {
     
    	NSError *error = nil;
    	if ([[self managedObjectContext] save:&error])
    	{
    		[self syncAction:sender];
    	}
    	else
    	{
    		[[NSApplication sharedApplication] presentError:error];
    	}
    }
  • If a synchronization involving our data is triggered by another application, we need to know so that we can update our own application. So, firstly add an applicationDidFinishLaunching: method to set the syncAlertHandler to a selector for a second method that calls saveAction:
    - (void)applicationDidFinishLaunching:(NSNotification *)notification
    {
    	[[self syncClient] setSyncAlertHandler:self selector:@selector(client:mightWantToSyncEntityNames:)];
    	[self syncAction:nil];
    }
     
    - (void)client:(ISyncClient *)client mightWantToSyncEntityNames:(NSArray *)entityNames
    {
    	[self saveAction:self];
    }
  • Add an applicationWillTerminate: method that calls saveAction: so that our data is automatically saved and synchronized when the user exits the application:
    - (void)applicationWillTerminate:(NSNotification *)notification
    {
    	[self saveAction:nil];
    }
  • Next, add methods that specify which managedObjectContexts should be monitored when synchronizing our persistent store. If a synchronization is triggered outside of the application by another application synching with data in our schema, the managed object contexts listed in the second method will be reloaded to reflect any changes that were made:
    - (NSArray *)managedObjectContextsToMonitorWhenSyncingPersistentStoreCoordinator:(NSPersistentStoreCoordinator *)coordinator
    {
    	return [NSArray arrayWithObject:[self managedObjectContext]];
    }
     
    - (NSArray *)managedObjectContextsToReloadAfterSyncingPersistentStoreCoordinator:(NSPersistentStoreCoordinator *)coordinator
    {
    	return [NSArray arrayWithObject:[self managedObjectContext]];
    }
  • Finally, add methods that get called when an object is pushed or changed during synchronization. These methods allow information contained within the object or change to be modified to suit the way data is stored in our model. For this application, we can simply return the provided object as it stands:
    - (NSDictionary *)persistentStoreCoordinator:(NSPersistentStoreCoordinator *)coordinator willPushRecord:(NSDictionary *)record forManagedObject:(NSManagedObject *)managedObject inSyncSession:(ISyncSession *)session
    {
    	return record;
    }
     
    - (ISyncChange *)persistentStoreCoordinator:(NSPersistentStoreCoordinator *)coordinator willApplyChange:(ISyncChange *)change toManagedObject:(NSManagedObject *)managedObject inSyncSession:(ISyncSession *)session
    {
    	return change;
    }

Assuming all has gone to plan, when you build and run the project, you won’t notice very much difference, except that if you previously inputted some data into your application, you’ll probably experience a small delay before it is displayed while the synchronization is setup.

Apple provides a useful development tool called ‘Syncrospector’ that can be used to inspect sync schemas and clients. If you find and launch the tool you should be able to see your new sync schema listed, together with its specified entities. You can also view the sync client details for the DepartmentsAndEmployeesSyncApp application, hopefully displaying successful synchronization of the two entities:
The Synchrospector application inspecting our sync client details

Demo Application 2

Our second project is a simplified employee viewing application that holds the same data as our first but displays it in a slightly different way. We’ll define the client description and setup synching in a similar way to our first project such that the two applications will keep in sync with each other.

  • Create a new Core Data Application in Xcode and call it ‘EmployeesListSyncApp’.
  • Open the data model and add the same entities as before — Department and Person — with the same attributes and relationships.
  • Open MainWindow.nib in Interface Builder and add a single NSArrayController called ‘People’. Set it to Prepare Content from the ‘Person’ entity and bind its Managed Object Context to the application delegate’s ‘managedObjectContext’.
  • Open the Window and add an NSTableView. Give it four columns bound to the ‘firstName’, ‘lastName’, ‘salary’ and ‘department.name’ keys respectively from the People controller. Drag an NSNumberFormatter onto the Salary column and make the Department column uneditable.

Next we’ll define the client description for this application which is essentially the same as for our first application — namely that it synchronizes the ‘Department’ and ‘Person’ entities along with their various attributes and relationships.

  • Add a ‘Sync Client Description’ called ‘clientDescription’ to the Resources group of our project.
  • Change the Entities keys to access our DemoEmploymentData entities and attributes.

The finished clientDescription.plist file should look like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 
<!-- clientDescription.plist -->
<!-- EmployeesListSyncApp -->
<!-- Created by Tim Isted on 25/07/2008. -->
<!-- Copyright 2008 __MyCompanyName__. All rights reserved. -->
 
<plist version="1.0">
<dict>
	<key>DisplayName</key>
	<string>EmployeesListSyncApp</string>
 
	<key>Entities</key>
	<dict>
		<key>com.mycompany.DemoEmploymentData.Department</key>
		<array>
			<string>com.apple.syncservices.RecordEntityName</string>
			<string>name</string>
			<string>employees</string>
		</array>
 
		<key>com.mycompany.DemoEmploymentData.Person</key>
		<array>
			<string>com.apple.syncservices.RecordEntityName</string>
			<string>firstName</string>
			<string>lastName</string>
			<string>salary</string>
			<string>department</string>
		</array>
	</dict>
 
	<key>Type</key>
	<string>app</string>
</dict>
</plist>

Now open up the data model again and setup the synchronization for the entities and their attributes as we did in the first application.

  • The Data Class for both entities is ‘com.mycompany.DemoEmploymentData’.
  • Make the Department ‘name’ attribute and the Person ‘firstName’ and ‘lastName’ attributes into Identity properties.

Finally we can setup the application to synchronize data in a similar way to the first project:

  • Add the SyncServices framework to your project Linked Frameworks group.
  • #import the SyncServices/SyncServices.h header file into your application delegate header and change the interface to adopt the NSPersistentStoreCoordinatorSyncing protocol.
  • Since we’ll be synchronizing this application in the same way as the first, add the same - (void)syncAction:(id)sender; declaration in the header.
  • Change the implementation for the persistentStoreCoordinator: so it behaves as before:
    - (NSPersistentStoreCoordinator *) persistentStoreCoordinator
    {
    	if (persistentStoreCoordinator != nil) {
    		return persistentStoreCoordinator;
    	}
     
    	NSFileManager *fileManager;
    	NSString *applicationSupportFolder = nil;
    	NSURL *url;
    	NSError *error;
     
    	fileManager = [NSFileManager defaultManager];
    	applicationSupportFolder = [self applicationSupportFolder];
    	if ( ![fileManager fileExistsAtPath:applicationSupportFolder isDirectory:NULL] ) {
    		[fileManager createDirectoryAtPath:applicationSupportFolder attributes:nil];
    	}
     
    	url = [NSURL fileURLWithPath: [applicationSupportFolder stringByAppendingPathComponent: @"EmployeesListSyncApp.xml"]];
    	persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
     
    	NSPersistentStore *store = [persistentStoreCoordinator addPersistentStoreWithType:NSXMLStoreType configuration:nil URL:url options:nil error:&error];
     
    	if( store != nil ) {
    		NSURL *fastSyncFileURL = [NSURL fileURLWithPath:[applicationSupportFolder stringByAppendingPathComponent:@"EmployeesListSyncApp.fastsyncstore"]];
    		[persistentStoreCoordinator setStoresFastSyncDetailsAtURL:fastSyncFileURL forPersistentStore:store];
    	}
    	else {
    		[[NSApplication sharedApplication] presentError:error];
    	}
     
    	return persistentStoreCoordinator;
    }
  • Add the syncClient: method but this time we only need to register the client description, not the main sync schema:
    - (ISyncClient *)syncClient
    {
    	NSString *clientIdentifier = [[NSBundle mainBundle] bundleIdentifier];
    	NSString *reason = @"unknown error";
    	ISyncClient *client;
     
    	@try {
    		client = [[ISyncManager sharedManager] clientWithIdentifier:clientIdentifier];
    		if( client == nil )
    		{
    			client = [[ISyncManager sharedManager] registerClientWithIdentifier:clientIdentifier descriptionFilePath:[[NSBundle mainBundle] pathForResource:@"clientDescription" ofType:@"plist"]];
    			[client setShouldSynchronize:YES withClientsOfType:ISyncClientTypeApplication];
    			[client setShouldSynchronize:YES withClientsOfType:ISyncClientTypeDevice];
    			[client setShouldSynchronize:YES withClientsOfType:ISyncClientTypeServer];
    			[client setShouldSynchronize:YES withClientsOfType:ISyncClientTypePeer];
    		}
    	}
    	@catch (id exception) {
    		client = nil;
    		reason = [exception reason];
    	}
     
    	if( client == nil ) {
    		NSRunAlertPanel(@"You can not sync your Employee Data.", [NSString stringWithFormat:@"Failed to register the sync client: %@", reason], @"OK", nil, nil);
    	}
     
    	return client;
    }
  • Implement the syncAction: method as before:
    - (void)syncAction:(id)sender
    {
    	NSError *error = nil;
    	ISyncClient *client = [self syncClient];
    	if( client != nil )
    	{
    		[[[self managedObjectContext] persistentStoreCoordinator] syncWithClient:client inBackground:NO handler:self error:&error];
    	}
     
    	if( error != nil )
    	{
    		[[NSApplication sharedApplication] presentError:error];
    	}
    }
  • Change the saveAction: method to call the syncAction: method:
    - (IBAction) saveAction:(id)sender {
     
    	NSError *error = nil;
    	if ([[self managedObjectContext] save:&error])
    	{
    		[self syncAction:sender];
    	}
    	else
    	{
    		[[NSApplication sharedApplication] presentError:error];
    	}
    }
  • Finally add the other callback methods:
    - (void)applicationDidFinishLaunching:(NSNotification *)notification
    {
    	[[self syncClient] setSyncAlertHandler:self selector:@selector(client:mightWantToSyncEntityNames:)];
    	[self syncAction:nil];
    }
     
    - (void)client:(ISyncClient *)client mightWantToSyncEntityNames:(NSArray *)entityNames
    {
    	[self saveAction:self];
    }
     
    - (void)applicationWillTerminate:(NSNotification *)notification
    {
    	[self saveAction:nil];
    }
     
    - (NSArray *)managedObjectContextsToMonitorWhenSyncingPersistentStoreCoordinator:(NSPersistentStoreCoordinator *)coordinator
    {
    	return [NSArray arrayWithObject:[self managedObjectContext]];
    }
     
    - (NSArray *)managedObjectContextsToReloadAfterSyncingPersistentStoreCoordinator:(NSPersistentStoreCoordinator *)coordinator
    {
    	return [NSArray arrayWithObject:[self managedObjectContext]];
    }
     
    - (NSDictionary *)persistentStoreCoordinator:(NSPersistentStoreCoordinator *)coordinator willPushRecord:(NSDictionary *)record forManagedObject:(NSManagedObject *)managedObject inSyncSession:(ISyncSession *)session
    {
    	return record;
    }
     
    - (ISyncChange *)persistentStoreCoordinator:(NSPersistentStoreCoordinator *)coordinator willApplyChange:(ISyncChange *)change toManagedObject:(NSManagedObject *)managedObject inSyncSession:(ISyncSession *)session
    {
    	return change;
    }

If you build and run this second application, you’ll find that any data you might have input into the first application is automatically displayed by this one. Notice how the Department relationship is pulled across and correctly displayed.

Try changing the name or salary of one of the people in either application. Once you save or exit the program, you’ll find that the other application automatically updates the display (and its underlying data) accordingly. Create a new person in the first application; once you save, the second application will then display that new person too.

Sample Project Downloads

The sample projects are available in one file (136kb) here: coredatasyncserv.zip.

Once you have downloaded the file, decompress it and you’ll find three projects inside. The first two need to be built in a specific order as one requires the build release of the other:

  1. Open and build the DemoEmploymentData project.
  2. Open the DepartmentsAndEmployeesSyncApp project and check that the references to the DemoEmploymentData.syncschema are valid (ie they’re not displayed in red text). If they are not valid, delete the references and follow the instructions earlier in the article about adding the syncschema into your project. Once the references are valid, build and run the first application.
  3. Finally, open the EmployeesListSyncApp project and build/run it. There are no references to worry about inside this one!

If you run into problems, please let me know.

Share
4 Responses leave one →
  1. December 20, 2008

    Is it possible to use sync services to sync data across a network?

  2. October 18, 2010

    Great intro for me to sync services. Is there a way to use mobile me to sync between mac os and ios apps? I was a bit surprised to see that iOS core data doesn’t have sync service capabilities. Am I missing something?

  3. admin permalink*
    October 18, 2010

    Sadly, at the time of writing, there is no official Apple API for syncing Core Data to iOS via MobileMe etc.

    It’s a case of write your own sync engine, which is exceedingly non-trivial (!) or make use of a third-party solution, like Marcus Zarra’s ZSync (http://github.com/mzarra/ZSync).

  4. William Mitchell permalink
    July 7, 2011

    Tim,

    I have your book Core Data for iOS. Excellent all around! I wrote a five-star review and updated after completing the book. I learned a lot. I have contacted the publisher though because I cannot find the source code even after creating an account on their website and logging in. The book gives a link which brings up a descriptive page about this book but unless I am near blind, I find no link to the source code. I advised the publisher that I would update my review and note that the source has been made available. Meantime if you can send it to me, that would be wonderful. Best Regards.

Leave a Reply

Note: You can use basic XHTML in your comments. Your email address will never be published.

Subscribe to this comment feed via RSS