├── LICENSE.md ├── README.md ├── SLCoreDataStack.podspec └── SLCoreDataStack ├── NSManagedObjectContext+SLCoreDataStack.h ├── NSManagedObjectContext+SLCoreDataStack.m ├── SLCoreDataStack.h └── SLCoreDataStack.m /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) Copyright (c) 2013 Oliver Letterer, Sparrow-Labs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SLCoreDataStack 2 | 3 | `SLCoreDataStack` provides a CoreData stack managing two `NSManagedObjectContext`'s for you: 4 | 5 | * `SLCoreDataStack.backgroundThreadManagedObjectContext` with `NSPrivateQueueConcurrencyType`: Perform any changes to any CoreData model you are making here with the `-[NSManagedObjectContext performBlock:]` API. 6 | * `SLCoreDataStack.mainThreadManagedObjectContext` with `NSMainQueueConcurrencyType`: Use this context for displaying models in your UI. 7 | * `SLCoreDataStack` keeps these contexts is sync by automatically merging changes between them. 8 | * `SLCoreDataStack` supports automatic database migrations. For example: If you have three different model versions, then you can provide one migration from version 1 to version 2 and one migration from version 2 to version 3. `SLCoreDataStack` will find and detect available migrations and migrate an existing database under the hood for you. 9 | 10 | Check out [this blog post](http://floriankugler.com/blog/2013/4/29/concurrent-core-data-stack-performance-shootout) on why we chose this `NSManagedObjectContext` concept. 11 | 12 | ## Getting started 13 | 14 | ```objc 15 | NSURL *location = ...; // url to database.sqlite 16 | NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"MyModel" withExtension:@"momd"]; 17 | 18 | SLCoreDataStack *stack = [[SLCoreDataStack alloc] initWithType:NSSQLiteStoreType 19 | location:location 20 | model:modelURL 21 | inBundle:[NSBundle mainBundle]]; 22 | ``` 23 | 24 | Store the stack somewhere and make it accessible as needed. You are ready to go :) 25 | 26 | ## License 27 | 28 | MIT 29 | -------------------------------------------------------------------------------- /SLCoreDataStack.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'SLCoreDataStack' 3 | spec.version = '1.0.9' 4 | spec.platforms = { :ios => '8.0', :osx => '10.10', :tvos => '9.0', :watchos => '2.0' } 5 | spec.license = 'MIT' 6 | spec.source = { :git => 'https://github.com/OliverLetterer/SLCoreDataStack.git', :tag => spec.version.to_s } 7 | spec.source_files = 'SLCoreDataStack' 8 | spec.frameworks = 'Foundation', 'CoreData' 9 | spec.requires_arc = true 10 | spec.homepage = 'https://github.com/OliverLetterer/SLCoreDataStack' 11 | spec.summary = 'CoreData stack boilerplate' 12 | spec.author = { 'Oliver Letterer' => 'oliver.letterer@gmail.com' } 13 | end 14 | -------------------------------------------------------------------------------- /SLCoreDataStack/NSManagedObjectContext+SLCoreDataStack.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSManagedObjectContext+SLRESTfulCoreData.h 3 | // SLRESTfulCoreData 4 | // 5 | // The MIT License (MIT) 6 | // Copyright (c) 2013-2015 Oliver Letterer, Sparrow-Labs 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import 28 | 29 | 30 | 31 | NS_ASSUME_NONNULL_BEGIN 32 | 33 | @interface NSManagedObjectContext (SLCoreDataStack) 34 | 35 | /** 36 | @param object can be an NSManagedObject, an NSManagedObjectID or one or multiple array(s) of these both. 37 | 38 | @example [context performBlock:... withObject:@[ object1, objectID2, @[ someOtherObject ] ]]; 39 | */ 40 | - (void)performBlock:(void (^__nullable)(id __nullable object, NSError *__nullable error))block withObject:(id)object; 41 | 42 | /** 43 | Performs a block with `performBlock:withObject:` but raises an exception in the error case. 44 | */ 45 | - (void)performUnsafeBlock:(void (^__nullable)(id object))block withObject:(id)object; 46 | 47 | @end 48 | 49 | NS_ASSUME_NONNULL_END 50 | -------------------------------------------------------------------------------- /SLCoreDataStack/NSManagedObjectContext+SLCoreDataStack.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSManagedObjectContext+SLRESTfulCoreData.m 3 | // SLRESTfulCoreData 4 | // 5 | // The MIT License (MIT) 6 | // Copyright (c) 2013-2015 Oliver Letterer, Sparrow-Labs 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import "NSManagedObjectContext+SLCoreDataStack.h" 28 | 29 | static id managedObjectIDCollector(id object) 30 | { 31 | if ([object isKindOfClass:[NSArray class]]) { 32 | NSArray *array = object; 33 | NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count]; 34 | 35 | for (id managedObject in array) { 36 | [newArray addObject:managedObjectIDCollector(managedObject)]; 37 | } 38 | 39 | return newArray; 40 | } else if ([object isKindOfClass:[NSDictionary class]]) { 41 | NSDictionary *dictionary = object; 42 | NSMutableDictionary *newDictionary = [NSMutableDictionary dictionaryWithCapacity:dictionary.count]; 43 | 44 | for (id key in dictionary) { 45 | newDictionary[key] = managedObjectIDCollector(dictionary[key]); 46 | } 47 | 48 | return newDictionary; 49 | } else if ([object isKindOfClass:[NSSet class]]) { 50 | NSSet *set = object; 51 | NSMutableSet *newSet = [NSMutableSet set]; 52 | 53 | for (id managedObject in set) { 54 | [newSet addObject:managedObjectIDCollector(managedObject)]; 55 | } 56 | 57 | return newSet; 58 | } else if ([object isKindOfClass:[NSManagedObject class]]) { 59 | return [object objectID]; 60 | } else if ([object isKindOfClass:[NSManagedObjectID class]]) { 61 | return object; 62 | } else if (!object) { 63 | return nil; 64 | } 65 | 66 | NSCAssert(NO, @"%@ is unsupported by performBlock:withObject:", object); 67 | return nil; 68 | } 69 | 70 | static id managedObjectCollector(id objectIDs, NSManagedObjectContext *context, NSError **error) 71 | { 72 | if ([objectIDs isKindOfClass:[NSArray class]]) { 73 | NSArray *array = objectIDs; 74 | NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count]; 75 | 76 | for (id object in array) { 77 | NSError *localError = nil; 78 | id result = managedObjectCollector(object, context, &localError); 79 | 80 | if (localError) { 81 | *error = localError; 82 | return nil; 83 | } 84 | 85 | [newArray addObject:result]; 86 | } 87 | 88 | return newArray; 89 | } else if ([objectIDs isKindOfClass:[NSDictionary class]]) { 90 | NSDictionary *dictionary = objectIDs; 91 | NSMutableDictionary *newDictionary = [NSMutableDictionary dictionaryWithCapacity:dictionary.count]; 92 | 93 | for (id key in dictionary) { 94 | NSError *localError = nil; 95 | id result = managedObjectCollector(dictionary[key], context, &localError); 96 | 97 | if (localError) { 98 | *error = localError; 99 | return nil; 100 | } 101 | 102 | newDictionary[key] = result; 103 | } 104 | 105 | return newDictionary; 106 | } else if ([objectIDs isKindOfClass:[NSSet class]]) { 107 | NSSet *set = objectIDs; 108 | NSMutableSet *newSet = [NSMutableSet setWithCapacity:set.count]; 109 | 110 | for (id object in set) { 111 | NSError *localError = nil; 112 | id result = managedObjectCollector(object, context, &localError); 113 | 114 | if (localError) { 115 | *error = localError; 116 | return nil; 117 | } 118 | 119 | [newSet addObject:result]; 120 | } 121 | 122 | return newSet; 123 | } else if ([objectIDs isKindOfClass:[NSManagedObjectID class]]) { 124 | NSError *localError = nil; 125 | NSManagedObject *managedObject = [context existingObjectWithID:objectIDs error:&localError]; 126 | 127 | if (localError) { 128 | *error = localError; 129 | return nil; 130 | } 131 | 132 | return managedObject; 133 | } else if (!objectIDs) { 134 | return nil; 135 | } 136 | 137 | NSCAssert(NO, @"%@ is unsupported by performBlock:withObject:", objectIDs); 138 | return nil; 139 | } 140 | 141 | 142 | 143 | @implementation NSManagedObjectContext (SLCoreDataStack) 144 | 145 | - (void)performBlock:(void (^)(id object, NSError *error))block withObject:(id)object 146 | { 147 | id objectIDs = managedObjectIDCollector(object); 148 | 149 | [self performBlock:^{ 150 | NSError *error = nil; 151 | 152 | if (block) { 153 | block(managedObjectCollector(objectIDs, self, &error), error); 154 | } 155 | }]; 156 | } 157 | 158 | - (void)performUnsafeBlock:(void (^)(id object))block withObject:(id)object 159 | { 160 | NSArray *callStackSymbols = [NSThread callStackSymbols]; 161 | 162 | [self performBlock:^(id object, NSError *error) { 163 | if (error != nil) { 164 | [NSException raise:NSInternalInconsistencyException format:@"performUnsafeBlock raised an error: %@, call stack: %@", error, callStackSymbols]; 165 | } 166 | 167 | if (block) { 168 | block(object); 169 | } 170 | } withObject:object]; 171 | } 172 | 173 | @end 174 | -------------------------------------------------------------------------------- /SLCoreDataStack/SLCoreDataStack.h: -------------------------------------------------------------------------------- 1 | // 2 | // The MIT License (MIT) 3 | // Copyright (c) 2013-2015 Oliver Letterer, Sparrow-Labs 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | // 23 | 24 | #import 25 | #import 26 | #import 27 | 28 | extern NSString *const SLCoreDataStackErrorDomain; 29 | 30 | enum { 31 | SLCoreDataStackMappingModelNotFound = 1, 32 | SLCoreDataStackManagedObjectModelNotFound 33 | }; 34 | 35 | 36 | 37 | @interface SLCoreDataStack : NSObject 38 | 39 | @property (nonatomic, readonly) NSBundle *bundle; 40 | 41 | @property (nonatomic, readonly) NSString *storeType; 42 | @property (nonatomic, readonly) NSURL *storeLocation; 43 | 44 | @property (nonatomic, readonly) NSURL *managedObjectModelURL; 45 | @property (nonatomic, readonly) NSManagedObjectModel *managedObjectModel; 46 | @property (nonatomic, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator; 47 | 48 | @property (nonatomic, readonly) NSManagedObjectContext *mainThreadManagedObjectContext; 49 | @property (nonatomic, readonly) NSManagedObjectContext *backgroundThreadManagedObjectContext; 50 | 51 | - (instancetype)init NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE; 52 | - (instancetype)initWithType:(NSString *)storeType location:(NSURL *)storeLocation model:(NSURL *)modelURL inBundle:(NSBundle *)bundle NS_DESIGNATED_INITIALIZER; 53 | 54 | + (instancetype)newConvenientSQLiteStackWithModel:(NSString *)model inBundle:(NSBundle *)bundle; 55 | 56 | @end 57 | 58 | 59 | 60 | @interface SLCoreDataStack (Singleton) 61 | 62 | + (instancetype)sharedInstance NS_UNAVAILABLE; 63 | 64 | @end 65 | 66 | 67 | @interface SLCoreDataStack (Migration) 68 | 69 | @property (nonatomic, readonly) BOOL requiresMigration; 70 | - (BOOL)migrateDataStore:(NSError **)error; 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /SLCoreDataStack/SLCoreDataStack.m: -------------------------------------------------------------------------------- 1 | // 2 | // The MIT License (MIT) 3 | // Copyright (c) 2013-2015 Oliver Letterer, Sparrow-Labs 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | // 23 | 24 | #import "SLCoreDataStack.h" 25 | #import 26 | #import 27 | #import 28 | 29 | static void class_swizzleSelector(Class class, SEL originalSelector, SEL newSelector) 30 | { 31 | Method origMethod = class_getInstanceMethod(class, originalSelector); 32 | Method newMethod = class_getInstanceMethod(class, newSelector); 33 | if(class_addMethod(class, originalSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) { 34 | class_replaceMethod(class, newSelector, method_getImplementation(origMethod), method_getTypeEncoding(origMethod)); 35 | } else { 36 | method_exchangeImplementations(origMethod, newMethod); 37 | } 38 | } 39 | 40 | 41 | 42 | NSString *const SLCoreDataStackErrorDomain = @"SLCoreDataStackErrorDomain"; 43 | 44 | @interface NSManagedObjectContext (SLCoreDataStackInternal) 45 | 46 | @property (nonatomic, strong) NSMutableArray *SLCoreDataStack_deallocationHandlers; 47 | - (void)SLCoreDataStack_addDeallocationHandler:(void(^)(__unsafe_unretained NSManagedObjectContext *context))handler; 48 | 49 | @end 50 | 51 | @implementation NSManagedObjectContext (SLCoreDataStackInternal) 52 | 53 | #pragma mark - setters and getters 54 | 55 | - (NSMutableArray *)SLCoreDataStack_deallocationHandlers 56 | { 57 | return objc_getAssociatedObject(self, @selector(SLCoreDataStack_deallocationHandlers)); 58 | } 59 | 60 | - (void)setSLCoreDataStack_deallocationHandlers:(NSMutableArray *)SLCoreDataStack_deallocationHandlers 61 | { 62 | objc_setAssociatedObject(self, @selector(SLCoreDataStack_deallocationHandlers), SLCoreDataStack_deallocationHandlers, OBJC_ASSOCIATION_RETAIN); 63 | } 64 | 65 | #pragma mark - instance methods 66 | 67 | - (void)SLCoreDataStack_addDeallocationHandler:(void(^)(__unsafe_unretained NSManagedObjectContext *context))handler 68 | { 69 | [self.SLCoreDataStack_deallocationHandlers addObject:handler]; 70 | } 71 | 72 | #pragma mark - Hooked implementations 73 | 74 | + (void)load 75 | { 76 | class_swizzleSelector(self, @selector(initWithConcurrencyType:), @selector(__SLCoreDataStackInitWithConcurrencyType:)); 77 | class_swizzleSelector(self, NSSelectorFromString(@"dealloc"), @selector(__SLCoreDataStackDealloc)); 78 | } 79 | 80 | - (id)__SLCoreDataStackInitWithConcurrencyType:(NSManagedObjectContextConcurrencyType)ct __attribute__((objc_method_family(init))) 81 | { 82 | if ((self = [self __SLCoreDataStackInitWithConcurrencyType:ct])) { 83 | self.SLCoreDataStack_deallocationHandlers = [NSMutableArray array]; 84 | } 85 | 86 | return self; 87 | } 88 | 89 | - (void)__SLCoreDataStackDealloc 90 | { 91 | for (id uncastedHandler in self.SLCoreDataStack_deallocationHandlers) { 92 | void(^handler)(__unsafe_unretained NSManagedObjectContext *context) = uncastedHandler; 93 | handler(self); 94 | } 95 | 96 | [self __SLCoreDataStackDealloc]; 97 | } 98 | 99 | @end 100 | 101 | 102 | 103 | @interface SLCoreDataStack () 104 | 105 | @property (nonatomic, strong) NSPointerArray *observingManagedObjectContexts; 106 | 107 | @end 108 | 109 | 110 | @implementation SLCoreDataStack 111 | @synthesize mainThreadManagedObjectContext = _mainThreadManagedObjectContext, backgroundThreadManagedObjectContext = _backgroundThreadManagedObjectContext, managedObjectModel = _managedObjectModel, persistentStoreCoordinator = _persistentStoreCoordinator; 112 | 113 | #pragma mark - setters and getters 114 | 115 | - (id)mainThreadMergePolicy 116 | { 117 | return NSMergeByPropertyObjectTrumpMergePolicy; 118 | } 119 | 120 | - (id)backgroundThreadMergePolicy 121 | { 122 | return NSMergeByPropertyObjectTrumpMergePolicy; 123 | } 124 | 125 | #pragma mark - Initialization 126 | 127 | + (instancetype)newConvenientSQLiteStackWithModel:(NSString *)model inBundle:(NSBundle *)bundle 128 | { 129 | NSURL *momURL = [bundle URLForResource:model withExtension:@"mom"]; 130 | NSURL *momdURL = [bundle URLForResource:model withExtension:@"momd"]; 131 | 132 | if (momURL && momdURL) { 133 | NSDate *momCreationDate = [[NSFileManager defaultManager] attributesOfItemAtPath:momURL.path error:NULL].fileCreationDate; 134 | NSDate *momdCreationDate = [[NSFileManager defaultManager] attributesOfItemAtPath:momdURL.path error:NULL].fileCreationDate; 135 | 136 | if (momCreationDate.timeIntervalSince1970 > momdCreationDate.timeIntervalSince1970) { 137 | NSLog(@"Found mom and momd model, will be using mom because fileCreationDate is newer"); 138 | momdURL = nil; 139 | } else { 140 | NSLog(@"Found mom and momd model, will be using momd because fileCreationDate is newer"); 141 | momURL = nil; 142 | } 143 | } 144 | 145 | NSAssert(momURL != nil || momdURL != nil, @"Neither %@.mom nor %@.momd could be found in bundle %@", model, model, bundle); 146 | NSURL *libraryDirectory = [[NSFileManager defaultManager] URLsForDirectory:NSLibraryDirectory 147 | inDomains:NSUserDomainMask].lastObject; 148 | 149 | NSURL *location = [libraryDirectory URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.sqlite", model]]; 150 | return [[self alloc] initWithType:NSSQLiteStoreType location:location model:momURL ?: momdURL inBundle:bundle]; 151 | } 152 | 153 | - (instancetype)init 154 | { 155 | return [super init]; 156 | } 157 | 158 | - (instancetype)initWithType:(NSString *)storeType location:(NSURL *)storeLocation model:(NSURL *)modelURL inBundle:(NSBundle *)bundle 159 | { 160 | if (self = [super init]) { 161 | _storeLocation = storeLocation; 162 | _storeType = storeType; 163 | _managedObjectModelURL = modelURL; 164 | _bundle = bundle; 165 | 166 | _observingManagedObjectContexts = [NSPointerArray pointerArrayWithOptions:NSPointerFunctionsWeakMemory]; 167 | 168 | NSString *parentDirectory = storeLocation.URLByDeletingLastPathComponent.path; 169 | if (![[NSFileManager defaultManager] fileExistsAtPath:parentDirectory isDirectory:NULL]) { 170 | NSError *error = nil; 171 | [[NSFileManager defaultManager] createDirectoryAtPath:parentDirectory 172 | withIntermediateDirectories:YES 173 | attributes:nil 174 | error:&error]; 175 | 176 | NSAssert(error == nil, @"error while creating parentDirectory '%@':\n\nerror: \"%@\"", parentDirectory, error); 177 | } 178 | 179 | #if TARGET_OS_IOS 180 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_automaticallySaveDataStore) 181 | name:UIApplicationWillTerminateNotification 182 | object:nil]; 183 | 184 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_automaticallySaveDataStore) 185 | name:UIApplicationDidEnterBackgroundNotification 186 | object:nil]; 187 | #elif TARGET_OS_WATCH 188 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_automaticallySaveDataStore) 189 | name:NSExtensionHostDidEnterBackgroundNotification 190 | object:nil]; 191 | #endif 192 | } 193 | return self; 194 | } 195 | 196 | - (void)dealloc 197 | { 198 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 199 | } 200 | 201 | #pragma mark - CoreData 202 | 203 | - (NSManagedObjectModel *)managedObjectModel 204 | { 205 | if (!_managedObjectModel) { 206 | _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:self.managedObjectModelURL]; 207 | NSParameterAssert(_managedObjectModel); 208 | } 209 | 210 | return _managedObjectModel; 211 | } 212 | 213 | - (NSManagedObjectContext *)mainThreadManagedObjectContext 214 | { 215 | if (!_mainThreadManagedObjectContext) { 216 | _mainThreadManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; 217 | _mainThreadManagedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator; 218 | _mainThreadManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy; 219 | 220 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_managedObjectContextDidSaveNotificationCallback:) name:NSManagedObjectContextDidSaveNotification object:_mainThreadManagedObjectContext]; 221 | } 222 | 223 | return _mainThreadManagedObjectContext; 224 | } 225 | 226 | - (NSManagedObjectContext *)backgroundThreadManagedObjectContext 227 | { 228 | if (!_backgroundThreadManagedObjectContext) { 229 | _backgroundThreadManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 230 | _backgroundThreadManagedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator; 231 | _backgroundThreadManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy; 232 | 233 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_managedObjectContextDidSaveNotificationCallback:) name:NSManagedObjectContextDidSaveNotification object:_backgroundThreadManagedObjectContext]; 234 | } 235 | 236 | return _backgroundThreadManagedObjectContext; 237 | } 238 | 239 | - (NSPersistentStoreCoordinator *)persistentStoreCoordinator 240 | { 241 | if (!_persistentStoreCoordinator) { 242 | NSURL *storeURL = self.storeLocation; 243 | NSManagedObjectModel *managedObjectModel = self.managedObjectModel; 244 | 245 | if (self.requiresMigration) { 246 | NSError *error = nil; 247 | if (![self migrateDataStore:&error]) { 248 | NSLog(@"[SLCoreDataStack] migrating data store failed: %@", error); 249 | } 250 | } 251 | 252 | NSDictionary *options = @{ 253 | NSMigratePersistentStoresAutomaticallyOption: @YES, 254 | NSInferMappingModelAutomaticallyOption: @YES 255 | }; 256 | 257 | NSError *error = nil; 258 | _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:managedObjectModel]; 259 | 260 | if (![_persistentStoreCoordinator addPersistentStoreWithType:self.storeType configuration:nil URL:storeURL options:options error:&error]) { 261 | NSLog(@"[SLCoreDataStack] could not add persistent store: %@", error); 262 | NSLog(@"[SLCoreDataStack] deleting old data store"); 263 | 264 | [[NSFileManager defaultManager] removeItemAtURL:storeURL error:NULL]; 265 | error = nil; 266 | 267 | if (![_persistentStoreCoordinator addPersistentStoreWithType:self.storeType configuration:nil URL:storeURL options:options error:&error]) { 268 | NSLog(@"[SLCoreDataStack] could not add persistent store: %@", error); 269 | abort(); 270 | } 271 | } 272 | } 273 | 274 | #ifdef DEBUG 275 | [self _enableCoreDataThreadDebugging]; 276 | #endif 277 | 278 | return _persistentStoreCoordinator; 279 | } 280 | 281 | - (NSManagedObjectContext *)newManagedObjectContextWithConcurrencyType:(NSManagedObjectContextConcurrencyType)concurrencyType 282 | { 283 | NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:concurrencyType]; 284 | context.persistentStoreCoordinator = self.persistentStoreCoordinator; 285 | context.mergePolicy = self.backgroundThreadMergePolicy; 286 | 287 | [self.observingManagedObjectContexts addPointer:(__bridge void *)context]; 288 | 289 | __weak typeof(self) weakSelf = self; 290 | [context SLCoreDataStack_addDeallocationHandler:^(NSManagedObjectContext *__unsafe_unretained context) { 291 | __strong typeof(weakSelf) strongSelf = weakSelf; 292 | 293 | NSUInteger index = NSNotFound; 294 | 295 | for (NSUInteger i = 0; i < strongSelf.observingManagedObjectContexts.count; i++) { 296 | void *pointer = [strongSelf.observingManagedObjectContexts pointerAtIndex:i]; 297 | 298 | if (pointer == (__bridge void *)context) { 299 | index = i; 300 | break; 301 | } 302 | } 303 | 304 | if (index != NSNotFound) { 305 | [strongSelf.observingManagedObjectContexts removePointerAtIndex:index]; 306 | } 307 | 308 | [[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:context]; 309 | }]; 310 | 311 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_managedObjectContextDidSaveNotificationCallback:) name:NSManagedObjectContextDidSaveNotification object:context]; 312 | 313 | return context; 314 | } 315 | 316 | #pragma mark - private implementation () 317 | 318 | - (NSArray *)_observingManagedObjectsContexts 319 | { 320 | NSMutableArray *observingManagedObjectsContexts = [NSMutableArray array]; 321 | 322 | NSManagedObjectContext *mainThreadManagedObjectContext = self.mainThreadManagedObjectContext; 323 | if (mainThreadManagedObjectContext) { 324 | [observingManagedObjectsContexts addObject:mainThreadManagedObjectContext]; 325 | } 326 | 327 | NSManagedObjectContext *backgroundThreadManagedObjectContext = self.backgroundThreadManagedObjectContext; 328 | if (backgroundThreadManagedObjectContext) { 329 | [observingManagedObjectsContexts addObject:backgroundThreadManagedObjectContext]; 330 | } 331 | 332 | for (NSManagedObjectContext *context in self.observingManagedObjectContexts) { 333 | if (context) { 334 | [observingManagedObjectsContexts addObject:context]; 335 | } 336 | } 337 | 338 | return observingManagedObjectsContexts; 339 | } 340 | 341 | - (void)_managedObjectContextDidSaveNotificationCallback:(NSNotification *)notification 342 | { 343 | NSManagedObjectContext *changedContext = notification.object; 344 | 345 | for (NSManagedObjectContext *otherContext in [self _observingManagedObjectsContexts]) { 346 | if (changedContext.persistentStoreCoordinator == otherContext.persistentStoreCoordinator && otherContext != changedContext) { 347 | if (changedContext == self.backgroundThreadManagedObjectContext) { 348 | [otherContext performBlockAndWait:^{ 349 | [otherContext mergeChangesFromContextDidSaveNotification:notification]; 350 | }]; 351 | } else { 352 | [otherContext performBlock:^{ 353 | [otherContext mergeChangesFromContextDidSaveNotification:notification]; 354 | }]; 355 | } 356 | } 357 | } 358 | } 359 | 360 | - (void)_automaticallySaveDataStore 361 | { 362 | for (NSManagedObjectContext *context in [self _observingManagedObjectsContexts]) { 363 | if (!context.hasChanges) { 364 | continue; 365 | } 366 | 367 | [context performBlock:^{ 368 | NSError *error = nil; 369 | if (![context save:&error]) { 370 | NSLog(@"WARNING: Error while automatically saving changes of data store of class %@: %@", self, error); 371 | } 372 | }]; 373 | } 374 | } 375 | 376 | - (void)_enableCoreDataThreadDebugging 377 | { 378 | @synchronized(self) { 379 | NSManagedObjectModel *model = _persistentStoreCoordinator.managedObjectModel; 380 | 381 | for (NSEntityDescription *entity in model.entities) { 382 | Class class = NSClassFromString(entity.managedObjectClassName); 383 | 384 | if (!class || objc_getAssociatedObject(class, _cmd)) { 385 | continue; 386 | } 387 | 388 | IMP implementation = imp_implementationWithBlock(^(id _self, NSString *key) { 389 | struct objc_super super = { 390 | .receiver = _self, 391 | .super_class = [class superclass] 392 | }; 393 | ((void(*)(struct objc_super *, SEL, id))objc_msgSendSuper)(&super, @selector(willAccessValueForKey:), key); 394 | }); 395 | class_addMethod(class, @selector(willAccessValueForKey:), implementation, "v@:@"); 396 | 397 | implementation = imp_implementationWithBlock(^(id _self, NSString *key) { 398 | struct objc_super super = { 399 | .receiver = _self, 400 | .super_class = [class superclass] 401 | }; 402 | ((void(*)(struct objc_super *, SEL, id))objc_msgSendSuper)(&super, @selector(willChangeValueForKey:), key); 403 | }); 404 | class_addMethod(class, @selector(willChangeValueForKey:), implementation, "v@:@"); 405 | 406 | objc_setAssociatedObject(class, _cmd, @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 407 | } 408 | } 409 | } 410 | 411 | @end 412 | 413 | 414 | 415 | @implementation SLCoreDataStack (Singleton) 416 | 417 | + (instancetype)sharedInstance 418 | { 419 | return nil; 420 | } 421 | 422 | @end 423 | 424 | 425 | 426 | @implementation SLCoreDataStack (Migration) 427 | 428 | - (BOOL)requiresMigration 429 | { 430 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 90000 431 | NSDictionary *options = @{ 432 | NSMigratePersistentStoresAutomaticallyOption: @YES, 433 | NSInferMappingModelAutomaticallyOption: @YES 434 | }; 435 | 436 | NSDictionary *sourceStoreMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:self.storeType URL:self.storeLocation options:options error:NULL]; 437 | #else 438 | NSDictionary *sourceStoreMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:self.storeType URL:self.storeLocation error:NULL]; 439 | #endif 440 | 441 | if (!sourceStoreMetadata) { 442 | return NO; 443 | } 444 | 445 | return ![self.managedObjectModel isConfiguration:nil compatibleWithStoreMetadata:sourceStoreMetadata]; 446 | } 447 | 448 | - (BOOL)migrateDataStore:(NSError **)error 449 | { 450 | static OSSpinLock lock = OS_SPINLOCK_INIT; 451 | 452 | OSSpinLockLock(&lock); 453 | 454 | NSDictionary *options = @{ 455 | NSMigratePersistentStoresAutomaticallyOption: @YES, 456 | NSInferMappingModelAutomaticallyOption: @YES 457 | }; 458 | 459 | NSError *addStoreError = nil; 460 | NSPersistentStoreCoordinator *persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel]; 461 | 462 | if ([persistentStoreCoordinator addPersistentStoreWithType:self.storeType configuration:nil URL:self.storeLocation options:options error:&addStoreError]) { 463 | NSLog(@"[SLCoreDataStack] automatic persistent store migration completed %@", options); 464 | OSSpinLockUnlock(&lock); 465 | return YES; 466 | } else { 467 | NSLog(@"[SLCoreDataStack] could not automatic migrate persistent store with %@", options); 468 | NSLog(@"[SLCoreDataStack] addStoreError = %@", addStoreError); 469 | } 470 | 471 | BOOL success = [self _performMigrationFromDataStoreAtURL:self.storeLocation toDestinationModel:self.managedObjectModel error:error]; 472 | OSSpinLockUnlock(&lock); 473 | 474 | return success; 475 | } 476 | 477 | - (BOOL)_performMigrationFromDataStoreAtURL:(NSURL *)dataStoreURL 478 | toDestinationModel:(NSManagedObjectModel *)destinationModel 479 | error:(NSError **)error 480 | { 481 | BOOL(^updateError)(NSInteger errorCode, NSString *description) = ^BOOL(NSInteger errorCode, NSString *description) { 482 | if (!error) { 483 | return NO; 484 | } 485 | 486 | NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: description }; 487 | *error = [NSError errorWithDomain:SLCoreDataStackErrorDomain code:errorCode userInfo:userInfo]; 488 | 489 | return NO; 490 | }; 491 | 492 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 90000 493 | NSDictionary *options = @{ 494 | NSMigratePersistentStoresAutomaticallyOption: @YES, 495 | NSInferMappingModelAutomaticallyOption: @YES 496 | }; 497 | 498 | NSDictionary *sourceStoreMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:self.storeType URL:dataStoreURL options:options error:error]; 499 | #else 500 | NSDictionary *sourceStoreMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:self.storeType URL:dataStoreURL error:error]; 501 | #endif 502 | 503 | if (!sourceStoreMetadata) { 504 | return NO; 505 | } 506 | 507 | if ([destinationModel isConfiguration:nil compatibleWithStoreMetadata:sourceStoreMetadata]) { 508 | return YES; 509 | } 510 | 511 | NSArray *bundles = @[ self.bundle ]; 512 | NSManagedObjectModel *sourceModel = [NSManagedObjectModel mergedModelFromBundles:bundles 513 | forStoreMetadata:sourceStoreMetadata]; 514 | 515 | if (!sourceModel) { 516 | return updateError(SLCoreDataStackManagedObjectModelNotFound, [NSString stringWithFormat:@"Unable to find NSManagedObjectModel for store metadata %@", sourceStoreMetadata]); 517 | } 518 | 519 | NSMutableArray *objectModelPaths = [NSMutableArray array]; 520 | NSArray *allManagedObjectModels = [self.bundle pathsForResourcesOfType:@"momd" 521 | inDirectory:nil]; 522 | 523 | for (NSString *managedObjectModelPath in allManagedObjectModels) { 524 | NSArray *array = [self.bundle pathsForResourcesOfType:@"mom" 525 | inDirectory:managedObjectModelPath.lastPathComponent]; 526 | 527 | [objectModelPaths addObjectsFromArray:array]; 528 | } 529 | 530 | NSArray *otherModels = [self.bundle pathsForResourcesOfType:@"mom" inDirectory:nil]; 531 | [objectModelPaths addObjectsFromArray:otherModels]; 532 | 533 | if (objectModelPaths.count == 0) { 534 | return updateError(SLCoreDataStackManagedObjectModelNotFound, [NSString stringWithFormat:@"No NSManagedObjectModel found in bundle %@", self.bundle]); 535 | } 536 | 537 | NSMappingModel *mappingModel = nil; 538 | NSManagedObjectModel *targetModel = nil; 539 | NSString *modelPath = nil; 540 | 541 | for (modelPath in objectModelPaths.reverseObjectEnumerator) { 542 | NSURL *modelURL = [NSURL fileURLWithPath:modelPath]; 543 | targetModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; 544 | mappingModel = [NSMappingModel mappingModelFromBundles:bundles 545 | forSourceModel:sourceModel 546 | destinationModel:targetModel]; 547 | 548 | if (mappingModel) { 549 | break; 550 | } 551 | } 552 | 553 | if (!mappingModel) { 554 | return updateError(SLCoreDataStackMappingModelNotFound, [NSString stringWithFormat:@"Unable to find NSMappingModel for store at URL %@", dataStoreURL]); 555 | } 556 | 557 | NSMigrationManager *migrationManager = [[NSMigrationManager alloc] initWithSourceModel:sourceModel 558 | destinationModel:targetModel]; 559 | 560 | NSString *modelName = modelPath.lastPathComponent.stringByDeletingPathExtension; 561 | NSString *storeExtension = dataStoreURL.path.pathExtension; 562 | 563 | NSString *storePath = dataStoreURL.path.stringByDeletingPathExtension; 564 | 565 | NSString *destinationPath = [NSString stringWithFormat:@"%@.%@.%@", storePath, modelName, storeExtension]; 566 | NSURL *destinationURL = [NSURL fileURLWithPath:destinationPath]; 567 | 568 | if (![migrationManager migrateStoreFromURL:dataStoreURL type:self.storeType options:nil withMappingModel:mappingModel toDestinationURL:destinationURL destinationType:self.storeType destinationOptions:nil error:error]) { 569 | return NO; 570 | } 571 | 572 | if (![[NSFileManager defaultManager] removeItemAtURL:dataStoreURL error:error]) { 573 | return NO; 574 | } 575 | 576 | if (![[NSFileManager defaultManager] moveItemAtURL:destinationURL toURL:dataStoreURL error:error]) { 577 | return NO; 578 | } 579 | 580 | return [self _performMigrationFromDataStoreAtURL:dataStoreURL 581 | toDestinationModel:destinationModel 582 | error:error]; 583 | } 584 | 585 | @end 586 | 587 | 588 | 589 | #ifdef DEBUG 590 | @implementation NSManagedObject (SLCoreDataStackCoreDataThreadDebugging) 591 | 592 | + (void)load 593 | { 594 | class_swizzleSelector(self, @selector(willChangeValueForKey:), @selector(__SLCoreDataStackCoreDataThreadDebuggingWillChangeValueForKey:)); 595 | class_swizzleSelector(self, @selector(willAccessValueForKey:), @selector(__SLCoreDataStackCoreDataThreadDebuggingWillAccessValueForKey:)); 596 | } 597 | 598 | - (void)__SLCoreDataStackCoreDataThreadDebuggingWillAccessValueForKey:(NSString *)key 599 | { 600 | #pragma clang diagnostic push 601 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 602 | NSManagedObjectContext *context = self.managedObjectContext; 603 | 604 | if (context && context.concurrencyType != NSConfinementConcurrencyType) { 605 | __block dispatch_queue_t queue = NULL; 606 | [context performBlockAndWait:^{ 607 | queue = dispatch_get_current_queue(); 608 | }]; 609 | 610 | NSAssert(queue == dispatch_get_current_queue(), @"wrong queue buddy"); 611 | } 612 | 613 | #pragma clang diagnostic pop 614 | 615 | [self __SLCoreDataStackCoreDataThreadDebuggingWillAccessValueForKey:key]; 616 | } 617 | 618 | - (void)__SLCoreDataStackCoreDataThreadDebuggingWillChangeValueForKey:(NSString *)key 619 | { 620 | #pragma clang diagnostic push 621 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 622 | NSManagedObjectContext *context = self.managedObjectContext; 623 | 624 | if (context) { 625 | __block dispatch_queue_t queue = NULL; 626 | [context performBlockAndWait:^{ 627 | queue = dispatch_get_current_queue(); 628 | }]; 629 | 630 | NSAssert(queue == dispatch_get_current_queue(), @"wrong queue buddy"); 631 | } 632 | 633 | #pragma clang diagnostic pop 634 | 635 | [self __SLCoreDataStackCoreDataThreadDebuggingWillChangeValueForKey:key]; 636 | } 637 | 638 | @end 639 | #endif 640 | --------------------------------------------------------------------------------