Created
August 20, 2015 00:33
-
-
Save hectr/51251fe09330591235cb to your computer and use it in GitHub Desktop.
Core Data stack with read-only main context
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2013, Héctor Marqués | |
// | |
// Permission to use, copy, modify, and/or distribute this software for any | |
// purpose with or without fee is hereby granted, provided that the above | |
// copyright notice and this permission notice appear in all copies. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH | |
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY | |
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, | |
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM | |
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR | |
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR | |
// PERFORMANCE OF THIS SOFTWARE. | |
#import <CoreData/CoreData.h> | |
@interface NSManagedObjectContext (MRManagedObjectContext) | |
- (BOOL)isReadOnlyContext; | |
@end | |
@interface MRManagedObjectContext : NSManagedObjectContext | |
@property (nonatomic, assign) BOOL throwsExceptionOnSave; | |
@property (nonatomic, assign) BOOL failsOnSave; | |
- (BOOL)save:(NSError *__autoreleasing *)errorPtr forced:(BOOL)forced; | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2013, Héctor Marqués | |
// | |
// Permission to use, copy, modify, and/or distribute this software for any | |
// purpose with or without fee is hereby granted, provided that the above | |
// copyright notice and this permission notice appear in all copies. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH | |
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY | |
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, | |
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM | |
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR | |
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR | |
// PERFORMANCE OF THIS SOFTWARE. | |
#import "MRManagedObjectContext.h" | |
@implementation NSManagedObjectContext (MRManagedObjectContext) | |
- (BOOL)isReadOnlyContext | |
{ | |
return NO; | |
} | |
@end | |
@implementation MRManagedObjectContext | |
- (BOOL)save:(NSError **const)errorPtr | |
{ | |
BOOL saved; | |
if (_failsOnSave) { | |
if (errorPtr) { | |
NSDictionary *const userInfo = @{ NSLocalizedDescriptionKey: NSLocalizedString(@"Save Error", nil) }; | |
*errorPtr = [NSError errorWithDomain:NSCocoaErrorDomain | |
code:NSPersistentStoreSaveError | |
userInfo:userInfo]; | |
} | |
if (_throwsExceptionOnSave) { | |
[NSException raise:NSGenericException format:@"Unsupported operation"]; | |
} | |
saved = NO; | |
} else { | |
if (_throwsExceptionOnSave) { | |
[NSException raise:NSGenericException format:@"Unsupported operation"]; | |
} else { | |
saved = [super save:errorPtr]; | |
} | |
} | |
return saved; | |
} | |
- (BOOL)save:(NSError *__autoreleasing *const)errorPtr forced:(BOOL const)forced | |
{ | |
BOOL saved; | |
if (forced) { | |
saved = [super save:errorPtr]; | |
} else { | |
saved = [self save:errorPtr]; | |
} | |
return saved; | |
} | |
#pragma mark - NSManagedObjectContext (MRManagedObjectContext) | |
- (BOOL)isReadOnlyContext | |
{ | |
return _throwsExceptionOnSave || _failsOnSave; | |
} | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2013, Héctor Marqués | |
// | |
// Permission to use, copy, modify, and/or distribute this software for any | |
// purpose with or without fee is hereby granted, provided that the above | |
// copyright notice and this permission notice appear in all copies. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH | |
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY | |
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, | |
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM | |
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR | |
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR | |
// PERFORMANCE OF THIS SOFTWARE. | |
#import <CoreData/CoreData.h> | |
@class MRManagedObjectContext; | |
/** | |
Simple Core Data Stack implementation. | |
*/ | |
@interface MRSimpleCoreDataStack : NSObject { | |
MRManagedObjectContext *_readOnlyContext; | |
NSManagedObjectModel *_managedObjectModel; | |
NSPersistentStoreCoordinator *_persistentStoreCoordinator; | |
} | |
/// Retrieves the shared instance of the stack. | |
+ (MRSimpleCoreDataStack *)sharedStack; | |
/// Frees the sharedStack. | |
+ (void)freeSharedStack; | |
@property (strong, nonatomic) NSString *persistentStoreFilename; | |
@property (strong, nonatomic) NSString *modelName; | |
/// Returns the managed object model. | |
/// If the model doesn't already exist, it is created from the application's model. | |
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel; | |
/// Returns the persistent store coordinator. | |
/// If the coordinator doesn't already exist, it is created and the application's store added to it. | |
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; | |
/// Returns the main managed object context. | |
/// If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application. | |
@property (readonly, strong, nonatomic) NSManagedObjectContext *readOnlyContext; | |
/// You MUST enclose any modification in a managed object within this method's block or `performSync:` one. | |
- (void)performAsync:(void(^)(NSManagedObjectContext *readWriteContext))block; | |
/// You MUST enclose any modification in a managed object within this method's block or `performAsync:` one. | |
- (void)performSync:(void(^)(NSManagedObjectContext *readWriteContext))block; | |
/// Saves the main managed object changes. | |
/// You MUST use this method for saving the changes made pushed to read-only context when saving the read-write contexts. | |
- (BOOL)persistChanges:(NSError **)errorPtr; | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2013, Héctor Marqués | |
// | |
// Permission to use, copy, modify, and/or distribute this software for any | |
// purpose with or without fee is hereby granted, provided that the above | |
// copyright notice and this permission notice appear in all copies. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH | |
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY | |
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, | |
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM | |
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR | |
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR | |
// PERFORMANCE OF THIS SOFTWARE. | |
#import "MRSimpleCoreDataStack.h" | |
#import "MRManagedObjectContext.h" | |
static dispatch_once_t __onceToken = 0L; | |
@interface MRSimpleCoreDataStack () | |
@end | |
@implementation MRSimpleCoreDataStack | |
+ (MRSimpleCoreDataStack *)sharedStack | |
{ | |
static MRSimpleCoreDataStack *instance = nil; | |
dispatch_once(&__onceToken, ^{ | |
instance = [[MRSimpleCoreDataStack alloc] init]; | |
}); | |
return instance; | |
} | |
+ (void)freeSharedStack | |
{ | |
__onceToken = 0L; | |
} | |
- (NSManagedObjectModel *)managedObjectModel | |
{ | |
if (_managedObjectModel != nil) { | |
return _managedObjectModel; | |
} | |
NSString *const modelName = self.modelName; | |
NSURL *const modelURL = [[NSBundle mainBundle] URLForResource:modelName withExtension:@"momd"]; | |
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; | |
NSAssert(_managedObjectModel, @"Model cannot be nil"); | |
return _managedObjectModel; | |
} | |
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator | |
{ | |
if (_persistentStoreCoordinator != nil) { | |
return _persistentStoreCoordinator; | |
} | |
NSString *const filename = self.persistentStoreFilename; | |
NSURL *const storeURL = [self.mr_applicationDocumentsDirectory URLByAppendingPathComponent:filename]; | |
NSError *error = nil; | |
NSPersistentStoreCoordinator *const persistentStoreCoordinator = | |
[self persistentStoreCoordinatorWithstoreURL:storeURL withRecoveredError:&error]; | |
NSAssert(error == nil, @"Unhandled error"); | |
return persistentStoreCoordinator; | |
} | |
- (NSPersistentStoreCoordinator *)persistentStoreCoordinatorWithstoreURL:(NSURL *)storeURL withRecoveredError:(NSError **const)errorPtr | |
{ | |
NSDictionary *const options = @{ NSMigratePersistentStoresAutomaticallyOption: @YES, | |
NSInferMappingModelAutomaticallyOption: @YES }; | |
NSError *error = nil; | |
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; | |
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) { | |
NSFileManager *const fileManager = NSFileManager.defaultManager; | |
NSString *const path = storeURL.path; | |
NSError *deleteError = nil; | |
if (errorPtr) { | |
*errorPtr = error; | |
if ([fileManager fileExistsAtPath:path] == NO) { | |
NSAssert(error == nil, @"Unhandled error"); | |
return nil; | |
} else { | |
NSString *const extension = [NSString stringWithFormat:@"%lu.bkp", (long int)NSDate.timeIntervalSinceReferenceDate]; | |
NSString *const copyPath = [path stringByAppendingPathExtension:extension]; | |
NSError *copyError = nil; | |
if ([fileManager copyItemAtPath:path toPath:copyPath error:©Error] == NO) { | |
NSAssert(error == nil, @"Unhandled error"); | |
} | |
} | |
if ([fileManager removeItemAtPath:path error:&deleteError] == NO) { | |
NSAssert(error == nil, @"Unhandled error"); | |
NSString *const extension = [NSString stringWithFormat:@"%lu.tmp", (long int)NSDate.timeIntervalSinceReferenceDate]; | |
storeURL = [storeURL URLByAppendingPathExtension:extension]; | |
NSAssert(error == nil, @"Unhandled error"); | |
} | |
return [self persistentStoreCoordinatorWithstoreURL:storeURL withRecoveredError:NULL]; | |
} else { | |
NSAssert(error == nil, @"Unhandled error"); | |
abort(); | |
} | |
} | |
return _persistentStoreCoordinator; | |
} | |
- (void)performAsync:(void (^const)(NSManagedObjectContext *moc))block | |
{ | |
NSManagedObjectContext *const moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; | |
moc.parentContext = self.readOnlyContext; | |
[moc performBlock:^{ | |
block(moc); | |
}]; | |
} | |
- (void)performSync:(void (^const)(NSManagedObjectContext *moc))block | |
{ | |
NSManagedObjectContext *const moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; | |
moc.parentContext = self.readOnlyContext; | |
[moc performBlockAndWait:^{ | |
block(moc); | |
}]; | |
} | |
- (NSManagedObjectContext *)readOnlyContext | |
{ | |
if (_readOnlyContext == nil) { | |
@synchronized(self) { | |
if (_readOnlyContext == nil) { | |
NSPersistentStoreCoordinator *const coordinator = [self persistentStoreCoordinator]; | |
if (coordinator != nil) { | |
_readOnlyContext = [[MRManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; | |
_readOnlyContext.failsOnSave = YES; | |
[_readOnlyContext setPersistentStoreCoordinator:coordinator]; | |
[_readOnlyContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy]; | |
} | |
} | |
} | |
} | |
return _readOnlyContext; | |
} | |
- (BOOL)persistChanges:(NSError **const)errorPtr | |
{ | |
__block BOOL saved; | |
if (_readOnlyContext.hasChanges) { | |
if (NSThread.isMainThread) { | |
saved = [_readOnlyContext save:errorPtr forced:YES]; | |
} else { | |
[_readOnlyContext performBlockAndWait:^{ | |
saved = [_readOnlyContext save:errorPtr forced:YES]; | |
}]; | |
} | |
} else { | |
saved = NO; | |
} | |
return saved; | |
} | |
#pragma mark Accessors | |
- (NSString *)persistentStoreFilename | |
{ | |
if (_persistentStoreFilename == nil) { | |
@synchronized(self) { | |
if (_persistentStoreFilename == nil) { | |
NSString *const applicationName = [NSBundle.mainBundle objectForInfoDictionaryKey:(__bridge NSString *)kCFBundleNameKey]; | |
_persistentStoreFilename = | |
[NSString stringWithFormat:@"%@.sqlite", applicationName]; | |
} | |
} | |
} | |
return _persistentStoreFilename; | |
} | |
- (NSString *)modelName | |
{ | |
if (_modelName == nil) { | |
@synchronized(self) { | |
if (_modelName == nil) { | |
_modelName = [NSBundle.mainBundle objectForInfoDictionaryKey:(__bridge NSString *)kCFBundleNameKey];; | |
} | |
} | |
} | |
return _modelName; | |
} | |
#pragma mark Private | |
- (NSURL *)mr_applicationDocumentsDirectory | |
{ | |
NSFileManager *const defaultManager = NSFileManager.defaultManager; | |
NSArray *const urls = | |
[defaultManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask]; | |
NSURL *const lastObject = urls.lastObject; | |
return lastObject; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment