├── README.md └── iCloudCoreDataStack ├── AppDelegate.h ├── AppDelegate.m ├── PersistentStack.h └── PersistentStack.m /README.md: -------------------------------------------------------------------------------- 1 | iCloudCoreDataStack 2 | =================== 3 | 4 | **WARNING**: iCloud Core Data is an outdated technology stack and has been [deprecated](https://mjtsai.com/blog/2016/06/17/the-deprecation-of-icloud-core-data/) by Apple. I strongly advise against anyone using this stack. 5 | 6 | A persistence stack for using Core Data with iCloud Sync in iOS7 7 | 8 | Adapted from code by Chris Eidhof for [objc.io #4](http://www.objc.io/issue-4/full-core-data-application.html). 9 | 10 | This is not a full implementation, but a template that can be used as the basis for any iOS 7 Core Data application requiring iCloud Sync. Please read the comments in `PersistentStack.m` for more implementation details. 11 | 12 | Core Data iCloud Sync became and order of magnitude easier to implement in iOS 7. However, at the time of writing there is still no official sample application from Apple showing how to implement it (the existing iOS 6 samples are now completely out of date). The iCloud related parts of the code here are brought together from information in WWDC '13 Session 207 [What's New in Core Data and iCloud](https://developer.apple.com/wwdc/videos/?id=207) and posts in the Apple Developer Forums. 13 | 14 | Still, even having seen the WWDC session video a number of times and having read a lot of Dev Forum posts, it's not immediately apparent exactly how straightforward implementing Core Data iCloud Sync has become with iOS 7. I created iCloudCoreDataStack to show just how simple it really is, and how little extra code is required above and beyond a regular Core Data implementation. 15 | 16 | You no longer need to know whether the user is using iCloud or not, or even has an iCloud account on their device. Core Data now transparently handles creating a local store for you in those situations. The only additions to the stack for using iCloud are passing the `NSPersistentStoreUbiquitousContentNameKey` key when adding the persistent store and subscribing to the 3 persistent store notifications. That's it. Core Data iCloud Sync just got a whole lot easier with iOS 7! 17 | -------------------------------------------------------------------------------- /iCloudCoreDataStack/AppDelegate.h: -------------------------------------------------------------------------------- 1 | @import UIKit; 2 | #import "PersistentStack.h" 3 | 4 | @interface AppDelegate : UIResponder 5 | 6 | @property (strong, nonatomic) UIWindow *window; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /iCloudCoreDataStack/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | #import "PersistentStack.h" 3 | 4 | @interface AppDelegate () 5 | 6 | @property (nonatomic, strong) NSManagedObjectContext* managedObjectContext; 7 | @property (nonatomic, strong) PersistentStack* persistentStack; 8 | 9 | @end 10 | 11 | @implementation AppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 14 | { 15 | self.persistentStack = [[PersistentStack alloc] initWithStoreURL:self.storeURL modelURL:self.modelURL]; 16 | self.managedObjectContext = self.persistentStack.managedObjectContext; 17 | 18 | // Override point for customization after application launch. 19 | return YES; 20 | } 21 | 22 | - (void)applicationWillResignActive:(UIApplication *)application 23 | { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application 29 | { 30 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 31 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 32 | [self.managedObjectContext save:NULL]; 33 | } 34 | 35 | - (void)applicationWillEnterForeground:(UIApplication *)application 36 | { 37 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | - (void)applicationDidBecomeActive:(UIApplication *)application 41 | { 42 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 43 | } 44 | 45 | - (void)applicationWillTerminate:(UIApplication *)application 46 | { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | - (NSURL*)storeURL 51 | { 52 | NSURL* documentsDirectory = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:NULL]; 53 | return [documentsDirectory URLByAppendingPathComponent:@"MyApp.sqlite"]; 54 | } 55 | 56 | - (NSURL*)modelURL 57 | { 58 | return [[NSBundle mainBundle] URLForResource:@"MyApp" withExtension:@"momd"]; 59 | } 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /iCloudCoreDataStack/PersistentStack.h: -------------------------------------------------------------------------------- 1 | @import Foundation; 2 | @import CoreData; 3 | 4 | @interface PersistentStack : NSObject 5 | 6 | - (id)initWithStoreURL:(NSURL *)storeURL modelURL:(NSURL *)modelURL; 7 | 8 | @property (nonatomic,strong,readonly) NSManagedObjectContext *managedObjectContext; 9 | 10 | @end -------------------------------------------------------------------------------- /iCloudCoreDataStack/PersistentStack.m: -------------------------------------------------------------------------------- 1 | #import "PersistentStack.h" 2 | 3 | @interface PersistentStack () 4 | 5 | @property (nonatomic,strong,readwrite) NSManagedObjectContext* managedObjectContext; 6 | @property (nonatomic,strong) NSURL* modelURL; 7 | @property (nonatomic,strong) NSURL* storeURL; 8 | 9 | @end 10 | 11 | @implementation PersistentStack 12 | 13 | - (id)initWithStoreURL:(NSURL*)storeURL modelURL:(NSURL*)modelURL 14 | { 15 | self = [super init]; 16 | if (self) { 17 | self.storeURL = storeURL; 18 | self.modelURL = modelURL; 19 | [self setupManagedObjectContext]; 20 | } 21 | return self; 22 | } 23 | 24 | - (void)setupManagedObjectContext 25 | { 26 | self.managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; 27 | self.managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy; 28 | self.managedObjectContext.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel]; 29 | 30 | 31 | __weak NSPersistentStoreCoordinator *psc = self.managedObjectContext.persistentStoreCoordinator; 32 | 33 | // iCloud notification subscriptions 34 | NSNotificationCenter *dc = [NSNotificationCenter defaultCenter]; 35 | [dc addObserver:self 36 | selector:@selector(storesWillChange:) 37 | name:NSPersistentStoreCoordinatorStoresWillChangeNotification 38 | object:psc]; 39 | 40 | [dc addObserver:self 41 | selector:@selector(storesDidChange:) 42 | name:NSPersistentStoreCoordinatorStoresDidChangeNotification 43 | object:psc]; 44 | 45 | [dc addObserver:self 46 | selector:@selector(persistentStoreDidImportUbiquitousContentChanges:) 47 | name:NSPersistentStoreDidImportUbiquitousContentChangesNotification 48 | object:psc]; 49 | 50 | NSError* error; 51 | // the only difference in this call that makes the store an iCloud enabled store 52 | // is the NSPersistentStoreUbiquitousContentNameKey in options. I use "iCloudStore" 53 | // but you can use what you like. For a non-iCloud enabled store, I pass "nil" for options. 54 | 55 | // Note that the store URL is the same regardless of whether you're using iCloud or not. 56 | // If you create a non-iCloud enabled store, it will be created in the App's Documents directory. 57 | // An iCloud enabled store will be created below a directory called CoreDataUbiquitySupport 58 | // in your App's Documents directory 59 | [self.managedObjectContext.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType 60 | configuration:nil 61 | URL:self.storeURL 62 | options:@{ NSPersistentStoreUbiquitousContentNameKey : @"iCloudStore" } 63 | error:&error]; 64 | if (error) { 65 | NSLog(@"error: %@", error); 66 | } 67 | } 68 | 69 | - (NSManagedObjectModel*)managedObjectModel 70 | { 71 | return [[NSManagedObjectModel alloc] initWithContentsOfURL:self.modelURL]; 72 | } 73 | 74 | // Subscribe to NSPersistentStoreDidImportUbiquitousContentChangesNotification 75 | - (void)persistentStoreDidImportUbiquitousContentChanges:(NSNotification*)note 76 | { 77 | NSLog(@"%s", __PRETTY_FUNCTION__); 78 | NSLog(@"%@", note.userInfo.description); 79 | 80 | NSManagedObjectContext *moc = self.managedObjectContext; 81 | [moc performBlock:^{ 82 | [moc mergeChangesFromContextDidSaveNotification:note]; 83 | 84 | // you may want to post a notification here so that which ever part of your app 85 | // needs to can react appropriately to what was merged. 86 | // An exmaple of how to iterate over what was merged follows, although I wouldn't 87 | // recommend doing it here. Better handle it in a delegate or use notifications. 88 | // Note that the notification contains NSManagedObjectIDs 89 | // and not NSManagedObjects. 90 | NSDictionary *changes = note.userInfo; 91 | NSMutableSet *allChanges = [NSMutableSet new]; 92 | [allChanges unionSet:changes[NSInsertedObjectsKey]]; 93 | [allChanges unionSet:changes[NSUpdatedObjectsKey]]; 94 | [allChanges unionSet:changes[NSDeletedObjectsKey]]; 95 | 96 | for (NSManagedObjectID *objID in allChanges) { 97 | // do whatever you need to with the NSManagedObjectID 98 | // you can retrieve the object from with [moc objectWithID:objID] 99 | } 100 | 101 | }]; 102 | } 103 | 104 | // Subscribe to NSPersistentStoreCoordinatorStoresWillChangeNotification 105 | // most likely to be called if the user enables / disables iCloud 106 | // (either globally, or just for your app) or if the user changes 107 | // iCloud accounts. 108 | - (void)storesWillChange:(NSNotification *)note { 109 | NSManagedObjectContext *moc = self.managedObjectContext; 110 | [moc performBlockAndWait:^{ 111 | NSError *error = nil; 112 | if ([moc hasChanges]) { 113 | [moc save:&error]; 114 | } 115 | 116 | [moc reset]; 117 | }]; 118 | 119 | // now reset your UI to be prepared for a totally different 120 | // set of data (eg, popToRootViewControllerAnimated:) 121 | // but don't load any new data yet. 122 | } 123 | 124 | // Subscribe to NSPersistentStoreCoordinatorStoresDidChangeNotification 125 | - (void)storesDidChange:(NSNotification *)note { 126 | // here is when you can refresh your UI and 127 | // load new data from the new store 128 | } 129 | 130 | 131 | @end --------------------------------------------------------------------------------