Last active
February 26, 2021 23:11
-
-
Save kharrison/a5a98dbc2f8a0008498dd84f29620c84 to your computer and use it in GitHub Desktop.
Objective-C wrapper for NSPersistentContainer - Easy Core Data setup with iOS 10
This file contains 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
// | |
// CoreDataController.h | |
// | |
// Created by Keith Harrison http://useyourloaf.com | |
// Copyright (c) 2016 Keith Harrison. All rights reserved. | |
// | |
// Redistribution and use in source and binary forms, with or without | |
// modification, are permitted provided that the following conditions are met: | |
// | |
// 1. Redistributions of source code must retain the above copyright | |
// notice, this list of conditions and the following disclaimer. | |
// | |
// 2. Redistributions in binary form must reproduce the above copyright | |
// notice, this list of conditions and the following disclaimer in the | |
// documentation and/or other materials provided with the distribution. | |
// | |
// 3. Neither the name of the copyright holder nor the names of its | |
// contributors may be used to endorse or promote products derived from | |
// this software without specific prior written permission. | |
// | |
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | |
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
// POSSIBILITY OF SUCH DAMAGE. | |
@import CoreData; | |
/** | |
`CoreDataController` is a simple wrapper class for setting up | |
and using a Core Data stack. | |
@warning Requires iOS 10 as it depends on the availability of | |
`NSPersistentContainer`. | |
*/ | |
NS_ASSUME_NONNULL_BEGIN | |
API_AVAILABLE(macosx(10.12),ios(10.0),tvos(10.0),watchos(3.0)) | |
@interface CoreDataController : NSObject | |
/** | |
The default directory for the persistent stores on the current platform. | |
@return An `NSURL` for the directory containing the persistent store(s). If the | |
persistent store does not exist it will be created by default in this location | |
when loaded. | |
*/ | |
+ (NSURL *)defaultDirectoryURL; | |
///--------------------- | |
/// @name Properties | |
///--------------------- | |
/** | |
A read-only flag indicating if the persistent store is loaded. | |
*/ | |
@property (readonly, assign, getter=isStoreLoaded) BOOL storeLoaded; | |
/** | |
The managed object context associated with the main queue (read-only). To | |
perform tasks on a private background queue see `performBackgroundTask:` and | |
`newPrivateContext`. | |
The context is configured to be generational and to automatically consume save | |
notifications from other contexts. | |
*/ | |
@property (readonly, strong) NSManagedObjectContext *viewContext; | |
/** | |
The `URL` of the persistent store for this Core Data Stack. If there are more | |
than one stores this property returns the first store it finds. The store may | |
not yet exist. It will be created at this URL by default when first loaded. | |
This is a readonly property to create a persistent store in a different | |
location use `loadStoreAtURL:withCompletionHandler`. To move an existing | |
persistent store use `replacePersistentStoreAtURL:withPersistentStoreFromURL:`. | |
*/ | |
@property (readonly, copy) NSURL *storeURL; | |
/** | |
A flag that indicates whether this store is read-only. Set this value to YES | |
before loading the persistent store if you want a read-only store (for example | |
if you are loading a store from the application bundle). | |
Default is NO. | |
*/ | |
@property (assign, getter=isReadOnly) BOOL readOnly; | |
/** | |
A flag that indicates whether the store is added asynchronously. Set this | |
value before loading the persistent store. | |
Default is YES. | |
*/ | |
@property (assign) BOOL shouldAddStoreAsynchronously; | |
/** | |
A flag that indicates whether the store should be migrated | |
automatically if the store model version does not match the | |
coordinators model version. | |
Set this value before loading the persistent store. | |
Default is YES. | |
*/ | |
@property (assign) BOOL shouldMigrateStoreAutomatically; | |
/** | |
A flag that indicates whether a mapping model should be inferred | |
when migrating a store. | |
Set this value before loading the persistent store. | |
Default is YES. | |
*/ | |
@property (assign) BOOL shouldInferMappingModelAutomatically; | |
///--------------------- | |
/// @name Initialization | |
///--------------------- | |
- (instancetype)init NS_UNAVAILABLE; | |
/** | |
Creates and returns a `CoreDataController` object. This is the designated | |
initializer for the class. It creates the managed object model, persistent | |
store coordinator and main managed object context but does not load the | |
persistent store. | |
@param name The name of the `NSManagedObjectModel` and by default the name used | |
for the persistent store | |
@return A `CoreDataController` object initialized with the given name. | |
*/ | |
- (instancetype)initWithName:(NSString *)name NS_DESIGNATED_INITIALIZER; | |
///--------------------------------- | |
/// @name Loading a Persistent Store | |
///--------------------------------- | |
/** | |
Load the persistent store. | |
@param handler This handler block is executed on the calling thread when the | |
loading of the persistent store has completed. | |
To override the default name and location of the persistent store use | |
`loadStoreAtURL:withCompletionHandler:`. | |
*/ | |
- (void)loadStoreWithCompletionHandler:(void(^)(NSError *))handler; | |
/** | |
Load the persistent store. | |
@param storeURL The URL for the location of the persistent store. It will be created if it does not exist. | |
@param handler This handler block is executed on the calling thread when the | |
loading of the persistent store has completed. | |
*/ | |
- (void)loadStoreAtURL:(NSURL *)storeURL withCompletionHandler:(void(^)(NSError * _Nullable))handler; | |
///---------------------------------- | |
/// @name Managing a Persistent Store | |
///---------------------------------- | |
/** | |
A flag indicating if the persistent store exists at the specified URL. | |
@param storeURL An `NSURL` object for the location of the peristent store. | |
@return YES if a file exists at the specified URL otherwise NO. | |
@warning This method checks if a file exists at the specified location but | |
does not verify if it is a valid persistent store. | |
*/ | |
- (BOOL)persistentStoreExistsAtURL:(NSURL *)storeURL; | |
/** | |
Replace a persistent store. | |
@param destinationURL An `NSURL` for the persistent store to be replaced. | |
@param sourceURL An `NSURL` for the source persistent store. | |
@return A flag indicating if the operation was successful. | |
*/ | |
- (BOOL)replacePersistentStoreAtURL:(NSURL *)destinationURL withPersistentStoreFromURL:(NSURL *)sourceURL; | |
/** | |
Destroy a persistent store. | |
@param storeURL An `NSURL` for the persistent store to be destroyed. | |
@return A flag indicating if the operation was successful. | |
*/ | |
- (BOOL)destroyPersistentStoreAtURL:(NSURL *)storeURL; | |
///---------------------------------- | |
/// @name Performing Background tasks | |
///---------------------------------- | |
/** | |
Execute a block on a new private queue context. | |
@param block A block to execute on a newly created private context. The context | |
is passed to the block as a paramater. | |
*/ | |
- (void)performBackgroundTask:(void(^)(NSManagedObjectContext *))block; | |
/** | |
Create and return a new private queue `NSManagedObjectContext`. The new context | |
is set to consume `NSManagedObjectContextSave` broadcasts automatically. | |
@return A new private managed object context. | |
*/ | |
- (NSManagedObjectContext *)newPrivateContext NS_RETURNS_RETAINED; | |
///------------------------ | |
/// @name NSManagedObjectID | |
///------------------------ | |
/** | |
Return an object ID for the specified URI representation if a matching | |
store is available. | |
@param url An `NSURL` containing a URI of a managed object. | |
@return An `NSManagedObjectID` or `nil`. | |
*/ | |
- (NSManagedObjectID *)managedObjectIDForURIRepresentation:(NSURL *)url; | |
@end | |
NS_ASSUME_NONNULL_END |
This file contains 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
// | |
// CoreDataController.m | |
// | |
// Created by Keith Harrison http://useyourloaf.com | |
// Copyright (c) 2016 Keith Harrison. All rights reserved. | |
// | |
// Redistribution and use in source and binary forms, with or without | |
// modification, are permitted provided that the following conditions are met: | |
// | |
// 1. Redistributions of source code must retain the above copyright | |
// notice, this list of conditions and the following disclaimer. | |
// | |
// 2. Redistributions in binary form must reproduce the above copyright | |
// notice, this list of conditions and the following disclaimer in the | |
// documentation and/or other materials provided with the distribution. | |
// | |
// 3. Neither the name of the copyright holder nor the names of its | |
// contributors may be used to endorse or promote products derived from | |
// this software without specific prior written permission. | |
// | |
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | |
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
// POSSIBILITY OF SUCH DAMAGE. | |
#import "CoreDataController.h" | |
@interface CoreDataController () | |
@property (nonatomic, getter=isStoreLoaded) BOOL storeLoaded; | |
@property (nonatomic, strong) NSPersistentContainer *persistentContainer; | |
@end | |
@implementation CoreDataController | |
+ (NSURL *)defaultDirectoryURL { | |
return [NSPersistentContainer defaultDirectoryURL]; | |
} | |
- (instancetype)initWithName:(NSString *)name { | |
NSManagedObjectModel *mom = [NSManagedObjectModel mergedModelFromBundles:nil]; | |
if (mom == nil) return nil; | |
self = [super init]; | |
if (self) { | |
_storeLoaded = NO; | |
_shouldAddStoreAsynchronously = YES; | |
_shouldMigrateStoreAutomatically = YES; | |
_shouldInferMappingModelAutomatically = YES; | |
_readOnly = NO; | |
_persistentContainer = [NSPersistentContainer persistentContainerWithName:name managedObjectModel:mom]; | |
} | |
return self; | |
} | |
- (void)loadStoreWithCompletionHandler:(void(^)(NSError *))handler { | |
[self loadStoreAtURL:self.storeURL withCompletionHandler:handler]; | |
} | |
- (void)loadStoreAtURL:(NSURL *)storeURL withCompletionHandler:(void(^)(NSError *))handler { | |
if (!self.persistentContainer) { | |
NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSNotFound userInfo:nil]; | |
if (handler) { | |
dispatch_async(dispatch_get_main_queue(), ^{ | |
handler(error); | |
}); | |
} | |
return; | |
} | |
self.persistentContainer.persistentStoreDescriptions = @[[self storeDescriptionWithURL:storeURL]]; | |
[self.persistentContainer loadPersistentStoresWithCompletionHandler: | |
^(NSPersistentStoreDescription *storeDescription, NSError *error) { | |
if (error == nil) { | |
self.storeLoaded = YES; | |
_persistentContainer.viewContext.automaticallyMergesChangesFromParent = YES; | |
} | |
if (handler) { | |
dispatch_async(dispatch_get_main_queue(), ^{ | |
handler(error); | |
}); | |
} | |
}]; | |
} | |
- (BOOL)persistentStoreExistsAtURL:(NSURL *)storeURL { | |
if (storeURL.isFileURL && | |
[NSFileManager.defaultManager fileExistsAtPath:storeURL.path]) { | |
return YES; | |
} | |
return NO; | |
} | |
- (NSURL *)storeURL { | |
NSArray *descriptions = self.persistentContainer.persistentStoreDescriptions; | |
NSPersistentStoreDescription *description = [descriptions firstObject]; | |
return description.URL; | |
} | |
- (BOOL)destroyPersistentStoreAtURL:(NSURL *)storeURL { | |
NSError *error = nil; | |
BOOL result = [self.persistentContainer.persistentStoreCoordinator destroyPersistentStoreAtURL:storeURL withType:NSSQLiteStoreType options:nil error:&error]; | |
return result; | |
} | |
- (BOOL)replacePersistentStoreAtURL:(NSURL *)destinationURL withPersistentStoreFromURL:(NSURL *)sourceURL { | |
NSError *error = nil; | |
BOOL result = [self.persistentContainer.persistentStoreCoordinator replacePersistentStoreAtURL:destinationURL destinationOptions:nil withPersistentStoreFromURL:sourceURL sourceOptions:nil storeType:NSSQLiteStoreType error:&error]; | |
return result; | |
} | |
- (NSManagedObjectContext *)viewContext { | |
return self.persistentContainer.viewContext; | |
} | |
- (NSManagedObjectContext *)newPrivateContext { | |
return [self.persistentContainer newBackgroundContext]; | |
} | |
- (void)performBackgroundTask:(void(^)(NSManagedObjectContext *))block { | |
[self.persistentContainer performBackgroundTask:block]; | |
} | |
- (NSManagedObjectID *)managedObjectIDForURIRepresentation:(NSURL *)url { | |
return [self.persistentContainer.persistentStoreCoordinator managedObjectIDForURIRepresentation:url]; | |
} | |
#pragma mark - | |
#pragma mark === Private methods === | |
#pragma mark - | |
- (NSPersistentStoreDescription *)storeDescriptionWithURL:(NSURL *)URL { | |
NSPersistentStoreDescription *description = [NSPersistentStoreDescription persistentStoreDescriptionWithURL:URL]; | |
description.shouldAddStoreAsynchronously = self.shouldAddStoreAsynchronously; | |
description.shouldMigrateStoreAutomatically = self.shouldMigrateStoreAutomatically; | |
description.shouldInferMappingModelAutomatically = self.shouldInferMappingModelAutomatically; | |
description.readOnly = self.isReadOnly; | |
return description; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment