Skip to content

Instantly share code, notes, and snippets.

@mluisbrown
Last active July 22, 2022 07:20
Show Gist options
  • Save mluisbrown/7015953 to your computer and use it in GitHub Desktop.
Save mluisbrown/7015953 to your computer and use it in GitHub Desktop.
PersistentStack for using Core Data with iCloud sync in iOS 7, adapted from code by Chris Eidhof for objc.io #4 (http://www.objc.io/issue-4/full-core-data-application.html). The most important thing is that you don't have to know whether the user is using iCloud or not, or even has an iCloud account on their device. Core Data now transparently h…
@import UIKit;
#import "PersistentStack.h"
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
#import "AppDelegate.h"
#import "PersistentStack.h"
@interface AppDelegate ()
@property (nonatomic, strong) NSManagedObjectContext* managedObjectContext;
@property (nonatomic, strong) PersistentStack* persistentStack;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.persistentStack = [[PersistentStack alloc] initWithStoreURL:self.storeURL modelURL:self.modelURL];
self.managedObjectContext = self.persistentStack.managedObjectContext;
// Override point for customization after application launch.
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
// 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.
// 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.
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// 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.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
[self.managedObjectContext save:NULL];
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// 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.
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// 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.
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
- (NSURL*)storeURL
{
NSURL* documentsDirectory = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:NULL];
return [documentsDirectory URLByAppendingPathComponent:@"MyApp.sqlite"];
}
- (NSURL*)modelURL
{
return [[NSBundle mainBundle] URLForResource:@"MyApp" withExtension:@"momd"];
}
@end
@import Foundation;
@import CoreData;
@interface PersistentStack : NSObject
- (id)initWithStoreURL:(NSURL *)storeURL modelURL:(NSURL *)modelURL;
@property (nonatomic,strong,readonly) NSManagedObjectContext *managedObjectContext;
@end
#import "PersistentStack.h"
@interface PersistentStack ()
@property (nonatomic,strong,readwrite) NSManagedObjectContext* managedObjectContext;
@property (nonatomic,strong) NSURL* modelURL;
@property (nonatomic,strong) NSURL* storeURL;
@end
@implementation PersistentStack
- (id)initWithStoreURL:(NSURL*)storeURL modelURL:(NSURL*)modelURL
{
self = [super init];
if (self) {
self.storeURL = storeURL;
self.modelURL = modelURL;
[self setupManagedObjectContext];
}
return self;
}
- (void)setupManagedObjectContext
{
self.managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
self.managedObjectContext.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
__weak NSPersistentStoreCoordinator *psc = self.managedObjectContext.persistentStoreCoordinator;
// iCloud notification subscriptions
NSNotificationCenter *dc = [NSNotificationCenter defaultCenter];
[dc addObserver:self
selector:@selector(storesWillChange:)
name:NSPersistentStoreCoordinatorStoresWillChangeNotification
object:psc];
[dc addObserver:self
selector:@selector(storesDidChange:)
name:NSPersistentStoreCoordinatorStoresDidChangeNotification
object:psc];
[dc addObserver:self
selector:@selector(persistentStoreDidImportUbiquitousContentChanges:)
name:NSPersistentStoreDidImportUbiquitousContentChangesNotification
object:psc];
NSError* error;
// the only difference in this call that makes the store an iCloud enabled store
// is the NSPersistentStoreUbiquitousContentNameKey in options. I use "iCloudStore"
// but you can use what you like. For a non-iCloud enabled store, I pass "nil" for options.
// Note that the store URL is the same regardless of whether you're using iCloud or not.
// If you create a non-iCloud enabled store, it will be created in the App's Documents directory.
// An iCloud enabled store will be created below a directory called CoreDataUbiquitySupport
// in your App's Documents directory
[self.managedObjectContext.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:self.storeURL
options:@{ NSPersistentStoreUbiquitousContentNameKey : @"iCloudStore" }
error:&error];
if (error) {
NSLog(@"error: %@", error);
}
}
- (NSManagedObjectModel*)managedObjectModel
{
return [[NSManagedObjectModel alloc] initWithContentsOfURL:self.modelURL];
}
// Subscribe to NSPersistentStoreDidImportUbiquitousContentChangesNotification
- (void)persistentStoreDidImportUbiquitousContentChanges:(NSNotification*)note
{
NSLog(@"%s", __PRETTY_FUNCTION__);
NSLog(@"%@", note.userInfo.description);
NSManagedObjectContext *moc = self.managedObjectContext;
[moc performBlock:^{
[moc mergeChangesFromContextDidSaveNotification:note];
// you may want to post a notification here so that which ever part of your app
// needs to can react appropriately to what was merged.
// An exmaple of how to iterate over what was merged follows, although I wouldn't
// recommend doing it here. Better handle it in a delegate or use notifications.
// Note that the notification contains NSManagedObjectIDs
// and not NSManagedObjects.
NSDictionary *changes = note.userInfo;
NSMutableSet *allChanges = [NSMutableSet new];
[allChanges unionSet:changes[NSInsertedObjectsKey]];
[allChanges unionSet:changes[NSUpdatedObjectsKey]];
[allChanges unionSet:changes[NSDeletedObjectsKey]];
for (NSManagedObjectID *objID in allChanges) {
// do whatever you need to with the NSManagedObjectID
// you can retrieve the object from with [moc objectWithID:objID]
}
}];
}
// Subscribe to NSPersistentStoreCoordinatorStoresWillChangeNotification
// most likely to be called if the user enables / disables iCloud
// (either globally, or just for your app) or if the user changes
// iCloud accounts.
- (void)storesWillChange:(NSNotification *)note {
NSManagedObjectContext *moc = self.managedObjectContext;
[moc performBlockAndWait:^{
NSError *error = nil;
if ([moc hasChanges]) {
[moc save:&error];
}
[moc reset];
}];
// now reset your UI to be prepared for a totally different
// set of data (eg, popToRootViewControllerAnimated:)
// but don't load any new data yet.
}
// Subscribe to NSPersistentStoreCoordinatorStoresDidChangeNotification
- (void)storesDidChange:(NSNotification *)note {
// here is when you can refresh your UI and
// load new data from the new store
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment