/
RKManagedObjectStore.m
291 lines (245 loc) · 13.5 KB
/
RKManagedObjectStore.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
//
// RKManagedObjectStore.m
// RestKit
//
// Created by Blake Watters on 9/22/09.
// Copyright (c) 2009-2012 RestKit. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import "RKManagedObjectStore.h"
#import "RKLog.h"
#import "RKPropertyInspector.h"
#import "RKPropertyInspector+CoreData.h"
#import "RKPathUtilities.h"
#import "RKInMemoryManagedObjectCache.h"
#import "RKFetchRequestManagedObjectCache.h"
#import "NSManagedObjectContext+RKAdditions.h"
// Set Logging Component
#undef RKLogComponent
#define RKLogComponent RKlcl_cRestKitCoreData
NSString * const RKSQLitePersistentStoreSeedDatabasePathOption = @"RKSQLitePersistentStoreSeedDatabasePathOption";
NSString * const RKManagedObjectStoreDidFailSaveNotification = @"RKManagedObjectStoreDidFailSaveNotification";
static RKManagedObjectStore *defaultStore = nil;
@interface RKManagedObjectStore ()
@property (nonatomic, strong, readwrite) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, strong, readwrite) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@property (nonatomic, strong, readwrite) NSManagedObjectContext *persistentStoreManagedObjectContext;
@property (nonatomic, strong, readwrite) NSManagedObjectContext *mainQueueManagedObjectContext;
@end
@implementation RKManagedObjectStore
+ (instancetype)defaultStore
{
return defaultStore;
}
+ (void)setDefaultStore:(RKManagedObjectStore *)managedObjectStore
{
@synchronized(defaultStore) {
defaultStore = managedObjectStore;
}
}
- (instancetype)initWithManagedObjectModel:(NSManagedObjectModel *)managedObjectModel
{
self = [super init];
if (self) {
self.managedObjectModel = managedObjectModel;
self.managedObjectCache = [RKFetchRequestManagedObjectCache new];
// Hydrate the defaultStore
if (! defaultStore) {
[RKManagedObjectStore setDefaultStore:self];
}
}
return self;
}
- (instancetype)initWithPersistentStoreCoordinator:(NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
self = [self initWithManagedObjectModel:persistentStoreCoordinator.managedObjectModel];
if (self) {
self.persistentStoreCoordinator = persistentStoreCoordinator;
}
return self;
}
- (id)init
{
NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:[NSBundle allBundles]];
return [self initWithManagedObjectModel:managedObjectModel];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)createPersistentStoreCoordinator
{
self.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
}
- (NSPersistentStore *)addInMemoryPersistentStore:(NSError **)error
{
if (! self.persistentStoreCoordinator) [self createPersistentStoreCoordinator];
return [self.persistentStoreCoordinator addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:error];
}
- (NSPersistentStore *)addSQLitePersistentStoreAtPath:(NSString *)storePath
fromSeedDatabaseAtPath:(NSString *)seedPath
withConfiguration:(NSString *)nilOrConfigurationName
options:(NSDictionary *)nilOrOptions
error:(NSError **)error
{
if (! self.persistentStoreCoordinator) [self createPersistentStoreCoordinator];
NSURL *storeURL = [NSURL fileURLWithPath:storePath];
if (seedPath) {
BOOL success = [self copySeedDatabaseIfNecessaryFromPath:seedPath toPath:storePath error:error];
if (! success) return nil;
}
NSDictionary *options = nil;
if (nilOrOptions) {
NSMutableDictionary *mutableOptions = [nilOrOptions mutableCopy];
[mutableOptions setObject:(seedPath ?: [NSNull null]) forKey:RKSQLitePersistentStoreSeedDatabasePathOption];
options = mutableOptions;
} else {
options = @{ RKSQLitePersistentStoreSeedDatabasePathOption: (seedPath ?: [NSNull null]),
NSMigratePersistentStoresAutomaticallyOption: @(YES),
NSInferMappingModelAutomaticallyOption: @(YES) };
}
/**
There seems to be trouble with combining configurations and migration. So do this in two steps: first, attach the store with NO configuration, but WITH migration options; then remove it and reattach WITH configuration, but NOT migration options.
http://blog.atwam.com/blog/2012/05/11/multiple-persistent-stores-and-seed-data-with-core-data/
http://stackoverflow.com/questions/1774359/core-data-migration-error-message-model-does-not-contain-configuration-xyz
*/
NSPersistentStore *persistentStore = [self.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:error];
if (! persistentStore) return nil;
if (! [self.persistentStoreCoordinator removePersistentStore:persistentStore error:error]) return nil;
NSDictionary *seedOptions = @{ RKSQLitePersistentStoreSeedDatabasePathOption: (seedPath ?: [NSNull null]) };
persistentStore = [self.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nilOrConfigurationName URL:storeURL options:seedOptions error:error];
if (! persistentStore) return nil;
// Exclude the SQLite database from iCloud Backups to conform to the iCloud Data Storage Guidelines
RKSetExcludeFromBackupAttributeForItemAtPath(storePath);
return persistentStore;
}
- (BOOL)copySeedDatabaseIfNecessaryFromPath:(NSString *)seedPath toPath:(NSString *)storePath error:(NSError **)error
{
if (NO == [[NSFileManager defaultManager] fileExistsAtPath:storePath]) {
NSError *localError;
if (![[NSFileManager defaultManager] copyItemAtPath:seedPath toPath:storePath error:&localError]) {
RKLogError(@"Failed to copy seed database from path '%@' to path '%@': %@", seedPath, storePath, [localError localizedDescription]);
if (error) *error = localError;
return NO;
}
}
return YES;
}
- (NSManagedObjectContext *)newChildManagedObjectContextWithConcurrencyType:(NSManagedObjectContextConcurrencyType)concurrencyType
{
NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:concurrencyType];
[managedObjectContext performBlockAndWait:^{
managedObjectContext.parentContext = self.persistentStoreManagedObjectContext;
managedObjectContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy;
}];
return managedObjectContext;
}
- (void)createManagedObjectContexts
{
NSAssert(!self.persistentStoreManagedObjectContext, @"Unable to create managed object contexts: A primary managed object context already exists.");
NSAssert(!self.mainQueueManagedObjectContext, @"Unable to create managed object contexts: A main queue managed object context already exists.");
// Our primary MOC is a private queue concurrency type
self.persistentStoreManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
self.persistentStoreManagedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator;
self.persistentStoreManagedObjectContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy;
// Create an MOC for use on the main queue
self.mainQueueManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.mainQueueManagedObjectContext.parentContext = self.persistentStoreManagedObjectContext;
self.mainQueueManagedObjectContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy;
// Merge changes from a primary MOC back into the main queue when complete
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handlePersistentStoreManagedObjectContextDidSaveNotification:)
name:NSManagedObjectContextDidSaveNotification
object:self.persistentStoreManagedObjectContext];
}
- (void)recreateManagedObjectContexts
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:self.persistentStoreManagedObjectContext];
self.persistentStoreManagedObjectContext = nil;
self.mainQueueManagedObjectContext = nil;
[self createManagedObjectContexts];
}
- (BOOL)resetPersistentStores:(NSError **)error
{
[self.mainQueueManagedObjectContext reset];
[self.persistentStoreManagedObjectContext reset];
NSError *localError;
for (NSPersistentStore *persistentStore in self.persistentStoreCoordinator.persistentStores) {
NSURL *URL = [self.persistentStoreCoordinator URLForPersistentStore:persistentStore];
BOOL success = [self.persistentStoreCoordinator removePersistentStore:persistentStore error:&localError];
if (success) {
if ([URL isFileURL]) {
if (! [[NSFileManager defaultManager] removeItemAtURL:URL error:&localError]) {
RKLogError(@"Failed to remove persistent store at URL %@: %@", URL, localError);
if (error) *error = localError;
return NO;
}
// Check for and remove an external storage directory
NSString *supportDirectoryName = [NSString stringWithFormat:@".%@_SUPPORT", [[URL lastPathComponent] stringByDeletingPathExtension]];
NSURL *supportDirectoryFileURL = [NSURL URLWithString:supportDirectoryName relativeToURL:[URL URLByDeletingLastPathComponent]];
BOOL isDirectory = NO;
if ([[NSFileManager defaultManager] fileExistsAtPath:[supportDirectoryFileURL path] isDirectory:&isDirectory]) {
if (isDirectory) {
if (! [[NSFileManager defaultManager] removeItemAtURL:supportDirectoryFileURL error:&localError]) {
RKLogError(@"Failed to remove persistent store Support directory at URL %@: %@", supportDirectoryFileURL, localError);
if (error) *error = localError;
return NO;
}
} else {
RKLogWarning(@"Found external support item for store at path that is not a directory: %@", [supportDirectoryFileURL path]);
}
}
} else {
RKLogDebug(@"Skipped removal of persistent store file: URL for persistent store is not a file URL. (%@)", URL);
}
// Reclone the persistent store from the seed path if necessary
if ([persistentStore.type isEqualToString:NSSQLiteStoreType]) {
NSString *seedPath = [persistentStore.options valueForKey:RKSQLitePersistentStoreSeedDatabasePathOption];
if (seedPath && ![seedPath isEqual:[NSNull null]]) {
success = [self copySeedDatabaseIfNecessaryFromPath:seedPath toPath:[persistentStore.URL path] error:&localError];
if (! success) {
RKLogError(@"Failed reset of SQLite persistent store: Failed to copy seed database.");
if (error) *error = localError;
return NO;
}
}
}
// Add a new store with the same options
NSPersistentStore *newStore = [self.persistentStoreCoordinator addPersistentStoreWithType:persistentStore.type
configuration:persistentStore.configurationName
URL:persistentStore.URL
options:persistentStore.options error:&localError];
if (! newStore) {
if (error) *error = localError;
return NO;
}
} else {
RKLogError(@"Failed reset of persistent store %@: Failed to remove persistent store with error: %@", persistentStore, localError);
if (error) *error = localError;
return NO;
}
}
[self recreateManagedObjectContexts];
return YES;
}
- (void)handlePersistentStoreManagedObjectContextDidSaveNotification:(NSNotification *)notification
{
RKLogDebug(@"persistentStoreManagedObjectContext was saved: merging changes to mainQueueManagedObjectContext");
RKLogTrace(@"Merging changes detailed in userInfo dictionary: %@", [notification userInfo]);
NSAssert([notification object] == self.persistentStoreManagedObjectContext, @"Received Managed Object Context Did Save Notification for Unexpected Context: %@", [notification object]);
[self.mainQueueManagedObjectContext performBlock:^{
[self.mainQueueManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}];
}
@end