Skip to content
Browse files

Modernized the source code. Improved migrations, making them asynchro…

…nous.
  • Loading branch information...
1 parent d74fbd6 commit cc81e93526bd9daf963b0b96386b561b8ac43cf9 @drewmccormack committed Feb 5, 2013
View
2 iCloudCoreDataTester.xcodeproj/project.pbxproj
@@ -38,6 +38,7 @@
072661DE15135B800015E115 /* iCloudCoreDataTester.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = iCloudCoreDataTester.entitlements; sourceTree = "<group>"; };
0741FCD1159B35AF00EC5A12 /* MCPersistentStoreMigrator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCPersistentStoreMigrator.h; sourceTree = "<group>"; };
0741FCD2159B35AF00EC5A12 /* MCPersistentStoreMigrator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCPersistentStoreMigrator.m; sourceTree = "<group>"; };
+ 075542DB16C11B6E00054A6B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = text; path = README.md; sourceTree = SOURCE_ROOT; };
07AB01D5157B54C300B24E3B /* MCCloudResetSentinel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCCloudResetSentinel.h; sourceTree = "<group>"; };
07AB01D6157B54C300B24E3B /* MCCloudResetSentinel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCCloudResetSentinel.m; sourceTree = "<group>"; };
07AB01D9157B555800B24E3B /* NSURLExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSURLExtensions.h; sourceTree = "<group>"; };
@@ -116,6 +117,7 @@
070CF2C31512406A00D0C8CA /* Supporting Files */ = {
isa = PBXGroup;
children = (
+ 075542DB16C11B6E00054A6B /* README.md */,
070CF2C41512406A00D0C8CA /* iCloudCoreDataTester-Info.plist */,
070CF2C51512406A00D0C8CA /* InfoPlist.strings */,
070CF2C81512406A00D0C8CA /* main.m */,
View
152 iCloudCoreDataTester/AppDelegate.m
@@ -180,7 +180,7 @@ -(IBAction)setupCoreDataStack:(id)sender
BOOL usingCloudStorage = [[NSUserDefaults standardUserDefaults] boolForKey:MCUsingCloudStorageDefault];
if ( usingCloudStorage ) {
strongSelf->sentinel = [[MCCloudResetSentinel alloc] initWithCloudStorageURL:strongSelf.cloudStoreURL];
- strongSelf->sentinel.delegate = self;
+ strongSelf->sentinel.delegate = strongSelf;
[strongSelf->sentinel updateDevicesList:NULL];
}
@@ -197,7 +197,6 @@ -(IBAction)removeLocalFiles:(id)sender
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:MCUsingCloudStorageDefault];
[[NSUserDefaults standardUserDefaults] synchronize];
[self removeApplicationDirectory];
- [self setupCoreDataStack:self];
}
-(NSManagedObjectModel *)managedObjectModel
@@ -255,11 +254,6 @@ -(void)makePersistentStoreCoordinator
-(void)addStoreToPersistentStoreCoordinator:(void (^)(BOOL success, NSError *error))completionBlock
{
if ( completionBlock ) completionBlock = [completionBlock copy];
- dispatch_queue_t completionQueue = dispatch_get_current_queue();
-
-#if !OS_OBJECT_USE_OBJC
- dispatch_retain(completionQueue);
-#endif
// Use cloud storage if iCloud is enabled, and the user default is set to YES.
NSURL *storeURL = self.cloudStoreURL;
@@ -297,11 +291,8 @@ -(void)addStoreToPersistentStoreCoordinator:(void (^)(BOOL success, NSError *err
[self.persistentStoreCoordinator unlock];
}
- dispatch_async(completionQueue, ^{
+ dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(nil != store, error);
-#if !OS_OBJECT_USE_OBJC
- dispatch_release(completionQueue);
-#endif
});
#if !OS_OBJECT_USE_OBJC
dispatch_release(serialQueue);
@@ -321,18 +312,10 @@ -(void)makeManagedObjectContext
-(void)checkIfCloudDataHasBeenReset:(void (^)(BOOL hasBeenReset))completionBlock
{
- dispatch_queue_t completionQueue = dispatch_get_current_queue();
-#if !OS_OBJECT_USE_OBJC
- dispatch_retain(completionQueue);
-#endif
-
BOOL usingCloudStorage = [[NSUserDefaults standardUserDefaults] boolForKey:MCUsingCloudStorageDefault];
if ( usingCloudStorage && !self.cloudStoreURL ) {
- dispatch_async(completionQueue, ^{
+ dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(YES);
-#if !OS_OBJECT_USE_OBJC
- dispatch_release(completionQueue);
-#endif
});
return;
}
@@ -342,20 +325,14 @@ -(void)checkIfCloudDataHasBeenReset:(void (^)(BOOL hasBeenReset))completionBlock
MCCloudResetSentinel *tempSentinel = [[MCCloudResetSentinel alloc] initWithCloudStorageURL:self.cloudStoreURL];
[tempSentinel stopMonitoringDevicesList];
[tempSentinel checkCurrentDeviceRegistration:^(BOOL deviceIsPresent) {
- dispatch_async(completionQueue, ^{
+ dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(!deviceIsPresent);
-#if !OS_OBJECT_USE_OBJC
- dispatch_release(completionQueue);
-#endif
});
}];
}
else {
- dispatch_async(completionQueue, ^{
+ dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(NO);
-#if !OS_OBJECT_USE_OBJC
- dispatch_release(completionQueue);
-#endif
});
}
}
@@ -378,11 +355,18 @@ -(IBAction)startSyncing:(id)sender
MCCloudResetSentinel *tempSentinel = [[MCCloudResetSentinel alloc] initWithCloudStorageURL:self.cloudStoreURL];
[tempSentinel stopMonitoringDevicesList];
[tempSentinel checkCurrentDeviceRegistration:^(BOOL deviceIsPresent) {
+ // This block finishes up after a migration
+ void(^postMigrationBlock)(void) = ^{
+ [[NSUserDefaults standardUserDefaults] setBool:YES forKey:MCUsingCloudStorageDefault];
+ [[NSUserDefaults standardUserDefaults] synchronize];
+ [self setupCoreDataStack:self];
+ };
+
if ( deviceIsPresent ) {
// Only choice is to move data to the cloud, replacing the existing cloud data.
// In a production app, you should warn the user, and give them a chance
// to back out.
- [self migrateStoreToCloud];
+ [self migrateStoreToCloud:postMigrationBlock];
}
else {
// Can keep either the cloud data, or the local data at this point
@@ -392,94 +376,62 @@ -(IBAction)startSyncing:(id)sender
BOOL migrateDataFromCloud = [[NSFileManager defaultManager] fileExistsAtPath:self.cloudStoreURL.path];
if ( migrateDataFromCloud ) {
// Already cloud data present, so replace local data with it
- [self migrateStoreFromCloud];
+ [self migrateStoreFromCloud:postMigrationBlock];
}
else {
// No cloud data, so migrate local data to the cloud
- [self migrateStoreToCloud];
+ [self migrateStoreToCloud:postMigrationBlock];
}
}
- [[NSUserDefaults standardUserDefaults] setBool:YES forKey:MCUsingCloudStorageDefault];
- [[NSUserDefaults standardUserDefaults] synchronize];
- [self setupCoreDataStack:self];
}];
}
--(void)migrateStoreFromCloud
-{
- NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
- [defs setBool:YES forKey:MCUsingCloudStorageDefault];
- [defs synchronize];
- [self removeApplicationDirectory];
- [self setupCoreDataStack:self];
-}
-
--(void)migrateStoreToCloud
+-(void)migrateStoreAtURL:(NSURL *)storeURL fromOptions:(NSDictionary *)sourceOptions toOptions:(NSDictionary *)destinationOptions
{
- // Turn on syncing in prefs
- NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
- [defs setBool:YES forKey:MCUsingCloudStorageDefault];
- [defs synchronize];
-
- // Remove cloud files
- [self removeCloudData];
-
// Create URL for a temporary (old) store
__block NSError *error;
- NSURL *storeURL = self.localStoreURL;
NSURL *oldStoreURL = [[self applicationFilesDirectory] URLByAppendingPathComponent:@"OldStore"];
- NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSFileManager *fileManager = [[NSFileManager alloc] init];
// If there is no local store, no need to migrate
if ( ![fileManager fileExistsAtPath:storeURL.path] ) return;
-
+
// Remove any existing old store file left over from a previous migration
[fileManager removeItemAtURL:oldStoreURL error:NULL];
- // Move existing local store aside. Should do this in a coordinated manner.
+ // Move existing store aside. Should do this in a coordinated manner.
__block BOOL success = NO;
NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
- [fileCoordinator coordinateWritingItemAtURL:storeURL options:NSFileCoordinatorWritingForMoving error:NULL byAccessor:^(NSURL *url) {
- success = [fileManager moveItemAtURL:url toURL:oldStoreURL error:&error];
+ [fileCoordinator coordinateWritingItemAtURL:storeURL options:NSFileCoordinatorWritingForDeleting writingItemAtURL:oldStoreURL options:NSFileCoordinatorWritingForReplacing error:&error byAccessor:^(NSURL *newFromURL, NSURL *newToURL) {
+ success = [fileManager moveItemAtURL:newFromURL toURL:newToURL error:&error];
+ [fileCoordinator itemAtURL:newFromURL didMoveToURL:newToURL];
}];
+
if ( !success ) {
[[NSApplication sharedApplication] presentError:error];
return;
}
- // Options for new cloud store
- NSDictionary *localOnlyOptions = [NSDictionary dictionaryWithObjectsAndKeys:
- (id)kCFBooleanTrue, NSMigratePersistentStoresAutomaticallyOption,
- (id)kCFBooleanTrue, NSInferMappingModelAutomaticallyOption,
- (id)kCFBooleanTrue, NSReadOnlyPersistentStoreOption,
- nil];
- NSDictionary *cloudOptions = [NSDictionary dictionaryWithObjectsAndKeys:
- (id)kCFBooleanTrue, NSMigratePersistentStoresAutomaticallyOption,
- (id)kCFBooleanTrue, NSInferMappingModelAutomaticallyOption,
- MCCloudMainStoreFileName, NSPersistentStoreUbiquitousContentNameKey,
- self.cloudStoreURL, NSPersistentStoreUbiquitousContentURLKey,
- nil];
-
- // Here we use a migrator to keep memory low. If small store, could just use
+ // Here we use a migrator to keep memory low. If you have a small store, you could just use
// the NSPersistentStoreCoordinator method migratePersistentStore:toURL:options:withType:error:
MCPersistentStoreMigrator *migrator = [[MCPersistentStoreMigrator alloc] initWithManagedObjectModel:self.managedObjectModel sourceStoreURL:oldStoreURL destinationStoreURL:storeURL];
- migrator.sourceStoreOptions = localOnlyOptions;
- migrator.destinationStoreOptions = cloudOptions;
+ migrator.sourceStoreOptions = sourceOptions;
+ migrator.destinationStoreOptions = destinationOptions;
// Begin migration
BOOL migrationSucceeded = YES;
[migrator beginMigration];
- // Migrate the Note entity in batches of 100. This also migrates all objects connected to
- // the notes, either directly or indirectly.
+ // Migrate all entities. Relationships are always followed, unless they are 'snipped'.
// To demonstrate that you can 'snip' a object graph up into sub-graphs, to avoid migrating
// everything at once, we here snip a few relationships so that only Note and Facet objects are migrated first.
- // Note that you can only snip optional relationships, otherwise validation will fail when saving.
+ // You can only snip optional relationships, otherwise validation will fail when saving.
+ // Batch size of 0 is infinite, ie, no batching.
[migrator snipRelationship:@"permutations" inEntity:@"Note"];
[migrator snipRelationship:@"permutations" inEntity:@"Facet"];
- migrationSucceeded &= [migrator migrateEntityWithName:@"Note" batchSize:0 save:NO error:&error];
+ migrationSucceeded &= [migrator migrateEntityWithName:@"Note" batchSize:0 save:YES error:&error];
- // Migrate the Permutations (and connected MOs) in now. Batch size of 0 is infinite, ie, no batching.
+ // Migrate the Permutations (and connected MOs) in now. Batches of 10 at a time.
migrationSucceeded &= [migrator migrateEntityWithName:@"Permutation" batchSize:10 save:YES error:&error];
// End migration
@@ -493,10 +445,49 @@ -(void)migrateStoreToCloud
return;
}
else {
- [[NSFileManager defaultManager] removeItemAtURL:oldStoreURL error:NULL];
+ [fileManager removeItemAtURL:oldStoreURL error:NULL];
}
}
+-(void)migrateStoreFromCloud:(void(^)(void))completionBlock
+{
+ dispatch_async(dispatch_get_global_queue(0, 0), ^{
+ // Simply remove the local store file. Next setup, a new store will be created
+ // and populated with data from iCloud.
+ [self removeApplicationDirectory];
+
+ // Complete
+ dispatch_async(dispatch_get_main_queue(), completionBlock);
+ });
+}
+
+-(void)migrateStoreToCloud:(void(^)(void))completionBlock
+{
+ dispatch_async(dispatch_get_global_queue(0, 0), ^{
+ // Remove cloud files
+ [self removeCloudData];
+
+ // Options for new cloud store
+ NSDictionary *localOnlyOptions = @{
+ NSMigratePersistentStoresAutomaticallyOption: @YES,
+ NSInferMappingModelAutomaticallyOption: @YES,
+ NSReadOnlyPersistentStoreOption: @YES
+ };
+ NSDictionary *cloudOptions = @{
+ NSMigratePersistentStoresAutomaticallyOption: @YES,
+ NSInferMappingModelAutomaticallyOption: @YES,
+ NSPersistentStoreUbiquitousContentNameKey: MCCloudMainStoreFileName,
+ NSPersistentStoreUbiquitousContentURLKey: self.cloudStoreURL
+ };
+
+ // Migrate in place
+ [self migrateStoreAtURL:self.localStoreURL fromOptions:localOnlyOptions toOptions:cloudOptions];
+
+ // Complete
+ dispatch_async(dispatch_get_main_queue(), completionBlock);
+ });
+}
+
-(void)removeCloudData
{
NSURL *storeURL = self.cloudStoreURL;
@@ -530,7 +521,6 @@ -(IBAction)removeCloudFiles:(id)sender
[[NSUserDefaults standardUserDefaults] synchronize];
[self removeCloudData];
- [self setupCoreDataStack:self];
}
-(void)persistentStoreCoordinatorDidMergeCloudChanges:(NSNotification *)notification
View
20 iCloudCoreDataTester/MCCloudResetSentinel.m
@@ -126,11 +126,6 @@ -(void)checkCurrentDeviceRegistration:(void (^)(BOOL deviceIsRegistered))complet
{
if ( completionBlock ) completionBlock = [completionBlock copy];
-#if !OS_OBJECT_USE_OBJC
- dispatch_queue_t completionQueue = dispatch_get_current_queue();
- dispatch_retain(completionQueue);
-#endif
-
NSURL *url = self.syncedDevicesListURL;
[url syncWithCloud:^(BOOL succeeded, NSError *error) {
if ( !succeeded ) NSLog(@"%@", error);
@@ -146,11 +141,8 @@ -(void)checkCurrentDeviceRegistration:(void (^)(BOOL deviceIsRegistered))complet
NSString *defaultsDataset = [[NSUserDefaults standardUserDefaults] stringForKey:MCCloudResetSentinelSyncDataSetIDUserDefaultKey];
deviceIsRegistered &= [dataset isEqualToString:defaultsDataset];
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ dispatch_async(dispatch_get_main_queue(), ^{
if ( completionBlock ) completionBlock(deviceIsRegistered);
-#if !OS_OBJECT_USE_OBJC
- dispatch_release(completionQueue);
-#endif
});
}];
if ( error ) NSLog(@"%@", error);
@@ -182,11 +174,6 @@ -(void)updateDevicesList:(void (^)(void))completionBlock
if ( completionBlock ) completionBlock = [completionBlock copy];
-#if !OS_OBJECT_USE_OBJC
- dispatch_queue_t completionQueue = dispatch_get_current_queue();
- dispatch_retain(completionQueue);
-#endif
-
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[url syncWithCloud:^(BOOL succeeded, NSError *error) {
if ( !succeeded ) return;
@@ -235,11 +222,8 @@ -(void)updateDevicesList:(void (^)(void))completionBlock
}];
}
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ dispatch_async(dispatch_get_main_queue(), ^{
if ( completionBlock ) completionBlock();
-#if !OS_OBJECT_USE_OBJC
- dispatch_release(completionQueue);
-#endif
});
}];
});
View
27 iCloudCoreDataTester/NSURLExtensions.m
@@ -3,22 +3,26 @@
@implementation NSURL (MCExtensions)
-- (void) syncWithCloud:(void (^)(BOOL success, NSError *error))completionBlock {
+-(void)syncWithCloud:(void (^)(BOOL success, NSError *error))completionBlock {
NSError *error;
NSNumber *downloaded;
BOOL success = [self getResourceValue:&downloaded forKey:NSURLUbiquitousItemIsDownloadedKey error:&error];
if ( !success ) {
// Resource doesn't exist
- completionBlock(YES, nil);
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completionBlock(YES, nil);
+ });
return;
}
if ( !downloaded.boolValue ) {
NSNumber *downloading;
BOOL success = [self getResourceValue:&downloading forKey:NSURLUbiquitousItemIsDownloadingKey error:&error];
if ( !success ) {
- completionBlock(NO, error);
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completionBlock(YES, nil);
+ });
return;
}
@@ -33,24 +37,15 @@ - (void) syncWithCloud:(void (^)(BOOL success, NSError *error))completionBlock {
// Download not complete. Schedule another check.
double delayInSeconds = 0.1;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
-
-#if !OS_OBJECT_USE_OBJC
- dispatch_queue_t queue = dispatch_get_current_queue();
- dispatch_retain(queue);
- dispatch_after(popTime, queue, ^{
- [self syncWithCloud:[completionBlock copy]];
- dispatch_release(queue);
- });
-#else
dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- [self syncWithCloud:[completionBlock copy]];
+ [self syncWithCloud:completionBlock];
});
-#endif
} else {
- completionBlock(YES, nil);
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completionBlock(YES, nil);
+ });
}
}
-
@end

0 comments on commit cc81e93

Please sign in to comment.
Something went wrong with that request. Please try again.