Mac OS X Tutorial
Implementing TICoreDataSync in a Mac OS X non-document-based Core Data application
This tutorial walks through adding the TICoreDataSync framework to a very simple, non-document-based desktop application.
The example app uses Dropbox sync, and is hard-wired to use a desktop Dropbox located at ~/Dropbox
. In a shipping app, you would obviously need to add UI to enable/disable sync, customize location, specify encryption settings, etc. The example app’s user interface is deliberately basic, “designed” to demonstrate the framework with minimal distractions.
This is the Mac equivalent of the iOS app developed in the iOS Tutorial.
The Notebook Application
The application stores notes, which can be assigned tags:
The GitHub repository includes a vanilla version of the app (excluding any sync code) in Examples/Tutorial/Notebook
. Alternatively, feel free to build it from scratch, or follow this tutorial first if you’d like to see how it’s put together.
The finished version is the same as the Notebook
example app, which can be found in Examples/Notebook/
.
Adding Necessary Files
The first step is to add the TICoreDataSync files to the project, along with two Cocoa frameworks you’ll need later.
-
Add the TICoreDataSync directory:
Right-click on the project, choose Add Files to “Notebook”…, then choose the
TICoreDataSync
directory (the one that contains theTICoreDataSync.h
file and six numbered directories): -
Add the System Configuration and Security frameworks:
To add a framework using Xcode 4, click the Notebook project icon in the Project Navigator (⌘-1), select the Notebook target, then the Summary tab, and click the + button under the Linked Frameworks and Libraries list:
Add the
SystemConfiguration.framework
, which you’ll need later to find out the display name of the computer, as well as theSecurity.framework
, which is needed by the encryption code.
Managed Object Requirements
In order for changes to be recognized, every managed object you wish to synchronize must be an instance of TICDSSynchronizedManagedObject
and have a ticdsSyncID
attribute, and every change must take place inside a TICDSSynchronizedManagedObjectContext
.
-
Add the Sync Attribute to the Entities in the Data Model:
Open
Notebook.xcdatamodeld
and select theNote
entity. Add a new String attribute calledticdsSyncID
, and mark it as indexed:Do the same for the
Tag
entity. -
Change the Managed Object Subclass:
Both the Note and Tag entities are set to use custom subclasses rather than be plain
NSManagedObject
s.Open the
TINBNote.h
file and change the@interface
to inherit fromTICDSSynchronizedManagedObject
. You’ll need to import theTICoreDataSync.h
file:#import "TICoreDataSync.h" @class TINBNote; @interface TINBNote : TICDSSynchronizedManagedObject { ...
Do the same for the
TINBTag
class description. -
Change the Core Data Stack to use a Synchronized Context:
The Notebook application was created using a standard Xcode template project, so the Core Data stack is set up in the
NotebookAppDelegate
.Find the
managedObjectContext
method and change it to instantiate a synchronized context:- (NSManagedObjectContext *)managedObjectContext { ... __managedObjectContext = [[TICDSSynchronizedManagedObjectContext alloc] init]; ... return __managedObjectContext; }
If you wish, change the return type of the method, as well as the attribute and property types in the header.
Before you can do anything else with TICoreDataSync, your application will need to register an instance of an application sync manager.
The Application Sync Manager
The TICDSApplicationSyncManager
is responsible for creating the initial remote file hierarchy for your application, if necessary, as well as the hierarchy specific to each registered client device. Its delegate callbacks allow you to configure how synchronization works, including specifying whether to use encryption.
The Notebook application will need to register the sync manager in the app delegate’s applicationDidFinishLaunching
method. You’ll also need to implement a few required delegate callbacks:
-
Adopt the App Sync Manager Delegate Protocol
Change the
@interface
inNotebookAppDelegate.h
to indicate that the class adopts theTICDSApplicationSyncManagerDelegate
protocol. You’ll need to import theTICoreDataSync.h
file:#import "TICoreDataSync.h" @interface NotebookAppDelegate : NSObject <NSApplicationDelegate, NSTokenFieldDelegate, TICDSApplicationSyncManagerDelegate> { ...
The protocol includes a few required methods, which you’ll implement later.
-
Fetch the Default Manager, Configure it for Dropbox, and Register it:
You can either allocate and initialize a new application sync manager instance, or simply request the
defaultApplicationSyncManager
, which will create and keep track of one for you.Either way, you’ll need to instantiate the right sync manager type. For desktop Dropbox, this is the File-Manager-Based manager.
Switch to the
applicationDidFinishLaunching:
implementation, and implement the following:- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { TICDSFileManagerBasedApplicationSyncManager *manager = [TICDSFileManagerBasedApplicationSyncManager defaultApplicationSyncManager];
Configure the sync manager to use
~/Dropbox
as the hardwired location (this must exist for the purposes of this tutorial):[manager setApplicationContainingDirectoryLocation: [NSURL fileURLWithPath: [@"~/Dropbox" stringByExpandingTildeInPath]]];
Get the unique sync identifier for this client, and generate one if it doesn’t already exist:
NSString *clientUuid = [[NSUserDefaults standardUserDefaults] stringForKey:@"NotebookAppSyncClientUUID"]; if( !clientUuid ) { clientUuid = [TICDSUtilities uuidString]; [[NSUserDefaults standardUserDefaults] setValue:clientUuid forKey:@"NotebookAppSyncClientUUID"]; }
Use a function from the System Configuration framework to find out the computer name. This will be used as the device description (human readable information to help a user distinguish between multiple registered devices):
CFStringRef name = SCDynamicStoreCopyComputerName(NULL,NULL); NSString *deviceDescription = [NSString stringWithString:(NSString *)name]; CFRelease(name);
Finally, register the sync manager and provide the information:
[manager registerWithDelegate:self globalAppIdentifier:@"com.timisted.notebook" uniqueClientIdentifier:clientUuid description:deviceDescription userInfo:nil]; }
Note that the
globalAppIdentifier
parameter must be the same for every client, whether iOS or Mac.You’ll need to import the System Configuration framework header to avoid compiler warnings.
#import <SystemConfiguration/SystemConfiguration.h>
-
Implement the Required Delegate Methods:
The
TICDSApplicationSyncManagerDelegate
protocol includes three required methods; if you don’t implement these, you’ll get compiler warnings when you build the project.The first required method will be called the very first time the app is registered by any client, to determine whether to use encryption. Once this delegate method is called, the application registration process is paused so you can present UI to ask the user. For now, simply continue registration without using encryption:
- (void)applicationSyncManagerDidPauseRegistrationToAskWhether\ ToUseEncryptionForFirstTimeRegistration: (TICDSApplicationSyncManager *)aSyncManager { [aSyncManager continueRegisteringWithEncryptionPassword:nil]; }
The second required method will be called the first time a client registers with an existing, encrypted remote sync setup. For now, just provide
nil
to continue:- (void)applicationSyncManagerDidPauseRegistrationToRequestPassword\ ForEncryptedApplicationSyncData: (TICDSApplicationSyncManager *)aSyncManager { [aSyncManager continueRegisteringWithEncryptionPassword:nil]; }
The third required method will be called when an existing, previously synchronized document is downloaded to a client. In a document-based application, you’d use this method to return a configured Document Sync Manager for that downloaded document, but since this is a non-document-based app, just return
nil
as this method won’t be called:- (TICDSDocumentSyncManager *)applicationSyncManager: (TICDSApplicationSyncManager *)aSyncManager preConfiguredDocumentSyncManagerForDownloadedDocumentWithIdentifier: (NSString *)anIdentifier atURL:(NSURL *)aFileURL { return nil; }
Look at the ShoppingList example application to see how this method should be implemented in a document-based app.
Once the application sync manager is registered, you’ll need to configure and register the document sync manager, responsible for synchronizing the application’s data.
The Document Sync Manager
The TICDSDocumentSyncManager
is responsible for creating the remote hierarchy specific to a document, downloading and uploading the entire store, performing a sync, and cleaning up unneeded files.
In a document-based application, you have one document sync manager per document. Although the Notebook application is a non-document-based application, you’ll need to think of it as being a document-based application that only ever has one document.
Typically, a document-based application would keep track of a unique document synchronization identifier for each document; the Shopping List application, for example, saves this identifier in the metadata of a document’s persistent store.
For a non-document-based application, this identifier can be hard-wired into the application. When the application sync manager has completed its registration, the document sync manager can fire up its registration.
-
Adopt the Document Sync Manager Delegate Protocol:
Start by changing the
@interface
inNotebookAppDelegate.h
by adding yet another delegate protocol,TICDSDocumentSyncManagerDelegate
:@interface NotebookAppDelegate : NSObject <... , TICDSDocumentSyncManagerDelegate> { ...
-
Keep Track of the Document Sync Manager:
Add a property declaration to the app delegate to keep track of the document sync manager:
@interface NotebookAppDelegate : NSObject <...> { ... TICDSDocumentSyncManager *_documentSyncManager; } ... @property (retain) TICDSDocumentSyncManager *documentSyncManager; @end
Synthesize the property and release the instance variable in the implementation:
@implementation NotebookAppDelegate ... @synthesize documentSyncManager = _documentSyncManager; - (void)dealloc { [_documentSyncManager release], _documentSyncManager = nil; ... [super dealloc]; } @end
You need to keep a reference to the document sync manager so that you can initiate future tasks like synchronization.
-
Register the Document Sync Manager:
Implement
applicationSyncManagerDidFinishRegistering:
to trigger the creation of the document sync manager when the application sync manager has registered:- (void)applicationSyncManagerDidFinishRegistering: (TICDSApplicationSyncManager *)aSyncManager { TICDSFileManagerBasedDocumentSyncManager *docSyncManager = [[TICDSFileManagerBasedDocumentSyncManager alloc] init];
Register it, using a hard-wired document identifier:
[docSyncManager registerWithDelegate:self appSyncManager:aSyncManager managedObjectContext:[self managedObjectContext] documentIdentifier:@"Notebook" description:@"Application's data" userInfo:nil];
Finally, set the property (which will retain it) and release it to balance the
alloc] init]
:[self setDocumentSyncManager:docSyncManager]; [docSyncManager release]; }
Note that if you didn’t change the type of the managed object context property earlier, you’ll need to typecast
[self managedObjectContext]
to be(TICDSSynchronizedManagedObjectContext *)
. -
Implement the Required Delegate Methods:
There are four required document sync manager delegate methods.
One is called if a conflict is found during the synchronization process. In a shipping app, you would probably want to ask the user how to proceed, but for this tutorial, just implement the method to continue synchronizing with the local change taking precedent:
- (void)documentSyncManager:(TICDSDocumentSyncManager *)aSyncManager didPauseSynchronizationAwaitingResolutionOfSyncConflict: (id)aConflict { [aSyncManager continueSynchronizationByResolvingConflictWithResolutionType: TICDSSyncConflictResolutionTypeLocalWins]; }
Another is called to find out the location on disk of the store file to be uploaded:1
- (NSURL *)documentSyncManager:(TICDSDocumentSyncManager *)aSyncManager URLForWholeStoreToUploadForDocumentWithIdentifier: (NSString *)anIdentifier description:(NSString *)aDescription userInfo:(NSDictionary *)userInfo { return [[self applicationFilesDirectory] URLByAppendingPathComponent:@"Notebook.storedata"]; }
The final required delegate methods are called if the remote file structure doesn’t exist for the document at the time of registration, or if the document has previously been deleted. In a shipping application, you might want to ask the user what to do, at least if the document was deleted. For now, just implement both to tell the document sync manager to continue registration:
- (void)documentSyncManager:(TICDSDocumentSyncManager *)aSyncManager didPauseRegistrationAsRemoteFileStructureDoesNotExist\ ForDocumentWithIdentifier:(NSString *)anIdentifier description:(NSString *)aDescription userInfo:(NSDictionary *)userInfo { [aSyncManager continueRegistrationByCreatingRemoteFileStructure:YES]; } - (void)documentSyncManager:(TICDSDocumentSyncManager *)aSyncManager didPauseRegistrationAsRemoteFileStructureWasDeleted\ ForDocumentWithIdentifier:(NSString *)anIdentifier description:(NSString *)aDescription userInfo:(NSDictionary *)userInfo { [aSyncManager continueRegistrationByCreatingRemoteFileStructure:YES]; }
Don’t run the app yet, as you need to determine what happens the first time a client tries to register.
Store Upload and Download
When a client registers, it should check whether it has existing data of its own. If not, it needs to download the most recent store that’s been uploaded by other registered clients, assuming such a store exists.
-
Keep Track of Whether to Download the Store:
Start by adding a
BOOL
instance variable and property:@interface NotebookAppDelegate : NSObject <...> { ... BOOL _downloadStoreAfterRegistering; } ... @property (nonatomic, assign, getter = shouldDownloadStoreAfterRegistering) BOOL downloadStoreAfterRegistering; @end
Synthesize the property in the implementation:
@implementation NotebookAppDelegate ... @synthesize downloadStoreAfterRegistering = _downloadStoreAfterRegistering; @end
-
Decide Whether to Download the Store:
You’ll need to add a check for existing data before the Core Data stack is set up. The easiest place to do this is just before the persistent store coordinator is created:
- (NSPersistentStoreCoordinator *) persistentStoreCoordinator { ... NSURL *url = [applicationFilesDirectory URLByAppendingPathComponent:@"Notebook.storedata"]; if( ![fileManager fileExistsAtPath:[url path]] ) { [self setDownloadStoreAfterRegistering:YES]; } __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom]; ... }
-
Download the Store After Registering:
If the store needs to be downloaded, this should be done just after the document sync manager finishes registering:
- (void)documentSyncManagerDidFinishRegistering: (TICDSDocumentSyncManager *)aSyncManager { if( [self shouldDownloadStoreAfterRegistering] ) { [[self documentSyncManager] initiateDownloadOfWholeStore]; } }
-
Don’t Download at First Launch:
If this is the very first time the app has been registered by any device, you won’t be able to download the store because no previous stores will exist.
As you saw earlier, one of the required delegate methods will be called by the document sync manager to find out what to do if no remote file structure exists for a document, or if the document has been deleted.
Change your implementation of these methods to prevent the store download:
- (void)documentSyncManager:(TICDSDocumentSyncManager *)aSyncManager didPauseRegistrationAsRemoteFileStructureDoesNotExist\ ForDocumentWithIdentifier:(NSString *)anIdentifier description:(NSString *)aDescription userInfo:(NSDictionary *)userInfo { [self setDownloadStoreAfterRegistering:NO]; [aSyncManager continueRegistrationByCreatingRemoteFileStructure:YES]; } - (void)documentSyncManager:(TICDSDocumentSyncManager *)aSyncManager didPauseRegistrationAsRemoteFileStructureWasDeleted\ ForDocumentWithIdentifier:(NSString *)anIdentifier description:(NSString *)aDescription userInfo:(NSDictionary *)userInfo { [self setDownloadStoreAfterRegistering:NO]; [aSyncManager continueRegistrationByCreatingRemoteFileStructure:YES]; }
-
Do Download if Client was Deleted:
If another client has previously deleted this client from synchronizing with the document, the underlying helper files will automatically be removed, but you will need to initiate a store download to override the whole store document file you have locally (as it will be out of date compared to the available sets of sync changes).
In a shipping application, you may want to copy the old store elsewhere in case the user wishes to restore it. For now, just implement the client deletion delegate warning method to indicate that the store should be downloaded.
Note that the registration process cannot be stopped at this point, so you do not need to call any
continueRegistration
method:- (void)documentSyncManagerDidDetermineThat\ ClientHadPreviouslyBeenDeletedFrom\ SynchronizingWithDocument:(TICDSDocumentSyncManager *)aSyncManager { [self setDownloadStoreAfterRegistering:YES]; }
-
Upload an Existing Store at Document Registration:
In order for other clients to be able to download the whole store, one client will obviously need to upload a copy of the store at some point.
The document sync manager will ask whether to upload the store during document registration. Implement this method to return
YES
, but only if this isn’t the first time this client has been registered:- (BOOL)documentSyncManagerShouldUploadWholeStore\ AfterDocumentRegistration:(TICDSDocumentSyncManager *)aSyncManager { return ![self shouldDownloadStoreAfterRegistering]; }
-
Handle Replacement of the Persistent Store File:
If the store file is downloaded, it will replace any file that has been created on disk.
You’ll need to implement two delegate methods to make sure the persistent store coordinator can cope with the file being removed.
First, implement the method called just before the store is replaced:
- (void)documentSyncManager:(TICDSDocumentSyncManager *)aSyncManager willReplaceStoreWithDownloadedStoreAtURL:(NSURL *)aStoreURL { NSError *anyError = nil; BOOL success = [[self persistentStoreCoordinator] removePersistentStore: [[self persistentStoreCoordinator] persistentStoreForURL:aStoreURL] error:&anyError]; if( !success ) { NSLog(@"Failed to remove persistent store at %@: %@", aStoreURL, anyError); } }
Second, the method called just after the store is replaced:
- (void)documentSyncManager:(TICDSDocumentSyncManager *)aSyncManager didReplaceStoreWithDownloadedStoreAtURL:(NSURL *)aStoreURL { NSError *anyError = nil; id store = [[self persistentStoreCoordinator] addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:aStoreURL options:nil error:&anyError]; if( !store ) { NSLog(@"Failed to add persistent store at %@: %@", aStoreURL, anyError); } }
Testing the Application
At this point, you’re ready to run the application to test store upload and download behavior, if you wish.
The first time you run the app on any device, you’ll find a directory is created in your ~/Dropbox
, called com.timisted.notebook
. This contains all the remote files used by TICoreDataSync to synchronize clients’ data. The file structure is described further in the Remote File Hierarchy document.
What’s missing at this point, however, is the main reason for using TICoreDataSync—the ability to synchronize changes made after the initial store upload/download.
Initiating Synchronization
The first thing to add is a suitable UI element to initiate synchronization.
-
Add an Action to the App Delegate:
Open
NotebookAppDelegate.h
and add the signature for anIBAction
method:@interface NotebookAppDelegate : NSObject <...> { ... } ... - (IBAction)beginSynchronizing:(id)sender; @end
Implement the method in
NotebookAppDelegate.m
, like this:- (IBAction)beginSynchronizing:(id)sender { [[self documentSyncManager] initiateSynchronization]; }
-
Add a Synchronize Button in the User Interface:
Open
MainMenu.xib
, add an unbordered button, with its image set toNSRefreshTemplate
.Connect the button’s selector to the
IBAction
: -
Check the Correct Action is Set for the Save Menu Item:
If you built the project from scratch, you may find that the Xcode template has connected the File > Save menu item to the first responder’s
saveDocument:
action, when it needs to be thesaveAction:
method.Check the connection before proceeding to make sure you’re connecting to the
saveAction:
method: -
Merge Changes Made During Synchronization:
When TICoreDataSync applies changes made by other clients, it does so in a background managed object context tied to the same persistent store coordinator as your primary context (the one you supplied when you registered the document sync manager).
You’ll need to implement another delegate method to alert you when changes are made and saved from the background context, so that you can merge them. This method passes you the
NSManagedObjectContextDidSave
notification object, which you can just pass straight to the primary context:- (void)documentSyncManager:(TICDSDocumentSyncManager *)aSyncManager didMakeChangesToObjectsInBackgroundContextAndSaveWithNotification: (NSNotification *)aNotification { [[self managedObjectContext] mergeChangesFromContextDidSaveNotification:aNotification]; }
Testing the Application
Build and run the application, add some notes and tags, then save the document. When you initiate a save, TICoreDataSync jumps into action to create Sync Change objects to describe what’s been changed. These are stored in a separate, private managed object context.
When you initiate a synchronization, any changes made by other clients are pulled down first. Any conflicts are fixed with the local, unpushed sync changes, then the local changes are pushed to the remote.
If you have more than one Mac, test to make sure each client pulls down the changes correctly.
Making Synchronization Appear Automatic
There are two other features you can implement to make synchronization appear more seamless.
Firstly, TICoreDataSync will ask whether it should initiate a synchronization whenever it detects that the primary context has been saved. If you respond with YES
, synchronization will occur every time the user saves their data.
Secondly, the File Manager-based sync option offers the ability to detect when other clients have pushed sync changes, at which point it will initiate a synchronization. In a sync environment of multiple desktop Macs, this means that when one client saves, the changes can immediately be pulled down by all the other synchronized clients.
-
Initiate Synchronization After Every Context Save:
Implement this document sync manager delegate method to return
YES
:- (BOOL)documentSyncManager:(TICDSDocumentSyncManager *)aSyncManager shouldBeginSynchronizingAfterManagedObjectContextDidSave: (TICDSSynchronizedManagedObjectContext *)aMoc { return YES; }
-
Enable Automatic Synchronization After Changes are Detected:
You’ll need to turn on remote change detection immediately after the document sync manager has finished registering, so add the following into the relevant delegate method:
- (void)documentSyncManagerDidFinishRegistering: (TICDSDocumentSyncManager *)aSyncManager { ... if( ![aSyncManager isKindOfClass: [TICDSFileManagerBasedDocumentSyncManager class]] ) { return; } [(TICDSFileManagerBasedDocumentSyncManager *)aSyncManager enableAutomaticSynchronizationAfterChangesDetectedFromOtherClients]; }
Testing the Application
Test the application once more, if possible on multiple desktop Macs, to check that these features work as expected.
If you follow the iOS-based Tutorial, any changes made by an iOS client will automagically appear on synchronized Macs (note that the iOS DropboxSDK-based document sync manager cannot automatically detect changes made by other clients).
Displaying Progress Indicators
It would be nice if the interface could display an animated progress indicator whenever synchronization tasks were taking place.
TICoreDataSync offers two ways to implement progress indication. For task-specific progress, you could implement every didBegin
, didFinish
, and didFailTo
delegate method, and display suitable progress updates. Alternatively, both application and document sync managers post notifications when they start and end a task.
Let’s take the easy approach, and use these notifications.
Using the Notifications
You’ll need to register for four notifications—two posted by the application sync manager, two by the document sync manager. These are intended to be used as indications when activity increases and decreases.
In a document-based application, you might display the application activity separately in an application-wide control panel, but for the Notebook app, it’s fine just to indicate both application and document activity in the same area.
-
Keep Track of the Activity Count and a Progress Indicator:
Start by adding an integer instance variable to keep track of the activity count, along with an
IBOutlet
property for a progress indicator:@interface NotebookAppDelegate : NSObject <...> { ... NSUInteger _activity; NSProgressIndicator *_activityIndicator; } ... @property (nonatomic, assign) IBOutlet NSProgressIndicator *activityIndicator; @end
The indicator will be hidden/shown and animated according to the activity count. You’ll add the indicator to the
xib
in a moment.Switch to the implementation (Ctrl-Cmd-UpArrow) and synthesize the progress indicator property:
@synthesize activityIndicator;
-
Register for Activity Notifications:
You’ll need to register for the application sync manger notifications just before you register:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { ... [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(activityDidIncrease:) name:TICDSApplicationSyncManagerDidIncreaseActivityNotification object:manager]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(activityDidDecrease:) name:TICDSApplicationSyncManagerDidDecreaseActivityNotification object:manager]; [manager registerWith... }
Similarly for the document sync manager notifications:
- (void)applicationSyncManagerDidFinishRegistering: (TICDSApplicationSyncManager *)aSyncManager { ... [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(activityDidIncrease:) name:TICDSDocumentSyncManagerDidIncreaseActivityNotification object:docSyncManager]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(activityDidDecrease:) name:TICDSDocumentSyncManagerDidDecreaseActivityNotification object:docSyncManager]; [docSyncManager registerWith... }
-
Implement the Activity Methods:
Add the following methods that will be called when notifications are posted:
- (void)activityDidIncrease:(NSNotification *)aNotification { _activity++; if( _activity > 0 ) { [[self activityIndicator] setHidden:NO]; [[self activityIndicator] startAnimation:self]; } } - (void)activityDidDecrease:(NSNotification *)aNotification { if( _activity > 0) { _activity--; } if( _activity < 1 ) { [[self activityIndicator] stopAnimation:self]; [[self activityIndicator] setHidden:YES]; } }
-
Add the Progress Indicator to the User Interface:
Open
MainMenu.xib
and drag out a circular progress indicator. Drop this next to the Synchronize button:Use the Attributes Inspector to mark it as hidden, then connect the indicator to the application delegate’s
activityIndicator
outlet.
Testing the Application
Test the application once again. The progress indicator should spin whenever activity is occurring.
Note that on the desktop, the majority of actions take place very quickly. The indicator may not appear for very long!
Adding Encryption
TICoreDataSync can encrypt all important synchronization data before it is transferred to the remote. In the case of desktop Dropbox in this Notebook application, this means that all synchronization files will be encrypted before they appear in the local ~/Dropbox/com.timisted.Notebook
directory.
Implementing the Encryption Methods
Encryption can only be enabled the first time any client registers to synchronize an application’s data. This means you’ll need to remove any existing remote sync data before continuing, either manually, or by asking the application sync manager to remove all data (not yet implemented in the framework).
-
Remove Existing Data:
If you’ve already launched the application and tested it by synchronizing data, you’ll need to quit the Notebook application on all clients, then delete the entire directory at
~/Dropbox/com.timisted.Notebook
. -
Re-Implement the Initial Registration Encryption Delegate Method:
You only need to modify two delegate methods to inform TICoreDataSync that it should encrypt all important data.
The first method is called the first time any client registers to synchronize data for an application:
- (void)applicationSyncManagerDidPauseRegistrationToAskWhether\ ToUseEncryptionForFirstTimeRegistration: (TICDSApplicationSyncManager *)aSyncManager {
The existing implementation of this method continues registration by passing
nil
as the password, meaning that the data won’t be encrypted.In a shipping application, you’d obviously want to display suitable UI to ask the user whether they want their data encrypted, and if so what password to use, but for this tutorial, just hard-wire a password.
Change the implementation of this method to specify a password:
[aSyncManager continueRegisteringWithEncryptionPassword: @"password"]; }
-
Re-Implement the Initial Client Registration Method:
The above method takes care of the first time an application is registered. For additional clients registering against existing data, the framework will detect if encryption is enabled, and request a password if necessary.
Again, in a shipping application you’d need to display suitable UI to ask the user for the password (if they supply an incorrect password, this method will be called repeatedly), but for this tutorial, just hard-wire the same password.
Change the implementation of the other encryption method to specify the password:
- (void)applicationSyncManagerDidPauseRegistrationToRequestPassword\ ForEncryptedApplicationSyncData: (TICDSApplicationSyncManager *)aSyncManager { [aSyncManager continueRegisteringWithEncryptionPassword: @"password"]; }
Testing the Application
Once again, test that the application behaves as expected. Nothing will appear to have changed from the user experience point of view, but if you try to open any of the files on the remote (ie., in ~/Dropbox/com.timisted.Notebook
) such as a deviceInfo.plist
file, you’ll find the content appears garbled and unreadable in a text editor.
-
This method makes use of an existing
↩applicationFilesDirectory
method provided by the original Xcode template. Depending on method order in the implementation, you may need to add a signature for the directory method to the@interface
to avoid compiler warnings.