Skip to content

Commit d072160

Browse files
committed
Extract creating and save context into a protocol
1 parent 4c4bc03 commit d072160

10 files changed

Lines changed: 217 additions & 151 deletions

WordPress/Classes/System/WordPress-Bridging-Header.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
#import "EditCommentViewController.h"
2929

30+
#import "LegacyContextFactory.h"
3031
#import "LocalCoreDataService.h"
3132

3233
#import "Media.h"

WordPress/Classes/Utility/ContextManager+ErrorHandling.swift

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,38 @@ private extension NSExceptionName {
5555
static let coreDataSaveDerivedException = NSExceptionName("Unresolved Core Data save error (Derived Context)")
5656
}
5757

58-
extension ContextManager {
59-
@objc(handleSaveError:inContext:)
58+
extension LegacyContextFactory {
59+
60+
/// A wrapper of `internalSave(_:)` to expose it as an Objective-C API for `LegacyContextFactory` to call.
61+
@objc func internalSaveContext(_ context: NSManagedObjectContext) {
62+
internalSave(context)
63+
}
64+
65+
}
66+
67+
extension ManagedObjectContextFactory {
68+
69+
func internalSave(_ context: NSManagedObjectContext) {
70+
let inserted = Array(context.insertedObjects)
71+
do {
72+
try context.obtainPermanentIDs(for: inserted)
73+
} catch {
74+
DDLogError("Error obtaining permanent object IDs for \(inserted), \(error)");
75+
}
76+
77+
if (context.hasChanges) {
78+
do {
79+
try context.save()
80+
} catch {
81+
handleSaveError(error as NSError, in: context)
82+
}
83+
}
84+
}
85+
86+
}
87+
88+
private extension ManagedObjectContextFactory {
89+
6090
func handleSaveError(_ error: NSError, in context: NSManagedObjectContext) {
6191
let isMainContext = context == mainContext
6292
let exceptionName: NSExceptionName = isMainContext ? .coreDataSaveMainException : .coreDataSaveDerivedException
@@ -68,9 +98,7 @@ extension ContextManager {
6898
let exception = NSException(name: exceptionName, reason: reason, userInfo: nil)
6999
exception.raise()
70100
}
71-
}
72101

73-
private extension ContextManager {
74102
func reasonForError(_ error: NSError) -> String {
75103
if error.code == NSValidationMultipleErrorsError {
76104
guard let errors = error.userInfo[NSDetailedErrorsKey] as? [NSError] else {

WordPress/Classes/Utility/ContextManager.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#import <Foundation/Foundation.h>
22
#import <CoreData/CoreData.h>
33

4+
#import "ManagedObjectContextFactory.h"
5+
46
NS_ASSUME_NONNULL_BEGIN
57

68
/**
@@ -59,8 +61,9 @@ FOUNDATION_EXTERN NSString * const ContextManagerModelNameCurrent;
5961
Use ContextManagerModelNameCurrent for current version, or
6062
"WordPress <version>" for specific version.
6163
@param storeURL Database location. Use +[ContextManager inMemoryStoreURL] to create an in-memory database.
64+
@param contextFactory A type that conforms to `ManagedObjectContextFactory`.
6265
*/
63-
- (instancetype)initWithModelName:(NSString *)modelName storeURL:(NSURL *)storeURL NS_DESIGNATED_INITIALIZER;
66+
- (instancetype)initWithModelName:(NSString *)modelName storeURL:(NSURL *)storeURL contextFactory:(Class<ManagedObjectContextFactory> _Nullable)factory NS_DESIGNATED_INITIALIZER;
6467

6568

6669
///--------------------------

WordPress/Classes/Utility/ContextManager.m

Lines changed: 19 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#import "ContextManager.h"
2+
#import "LegacyContextFactory.h"
23
#import "WordPress-Swift.h"
34
@import WordPressShared.WPAnalytics;
45
@import Foundation;
@@ -18,13 +19,11 @@ @interface ContextManager ()
1819

1920
@property (nonatomic, strong) NSPersistentStoreDescription *storeDescription;
2021
@property (nonatomic, strong) NSPersistentContainer *persistentContainer;
21-
@property (nonatomic, strong) NSPersistentStoreCoordinator *persistentStoreCoordinator;
2222
@property (nonatomic, strong) NSManagedObjectModel *managedObjectModel;
23-
@property (nonatomic, strong) NSManagedObjectContext *mainContext;
24-
@property (nonatomic, strong) NSManagedObjectContext *writerContext;
2523
@property (nonatomic, assign) BOOL migrationFailed;
2624
@property (nonatomic, strong) NSString *modelName;
2725
@property (nonatomic, strong) NSURL *storeURL;
26+
@property (nonatomic, strong) id<ManagedObjectContextFactory> contextFactory;
2827

2928
@end
3029

@@ -40,25 +39,27 @@ - (instancetype)init
4039
YES) lastObject];
4140
NSURL *storeURL = [NSURL fileURLWithPath:[documentsDirectory stringByAppendingPathComponent:@"WordPress.sqlite"]];
4241

43-
return [self initWithModelName:ContextManagerModelNameCurrent storeURL:storeURL];
42+
return [self initWithModelName:ContextManagerModelNameCurrent storeURL:storeURL contextFactory:[LegacyContextFactory class]];
4443
}
4544

46-
- (instancetype)initWithModelName:(NSString *)modelName storeURL:(NSURL *)storeURL
45+
- (instancetype)initWithModelName:(NSString *)modelName storeURL:(NSURL *)storeURL contextFactory:(Class)factory
4746
{
4847
self = [super init];
4948
if (self) {
49+
if (factory == nil) {
50+
factory = [LegacyContextFactory class];
51+
}
52+
5053
NSParameterAssert([modelName isEqualToString:ContextManagerModelNameCurrent] || [modelName hasPrefix:@"WordPress "]);
5154
NSParameterAssert([storeURL isFileURL]);
55+
NSParameterAssert([factory conformsToProtocol:@protocol(ManagedObjectContextFactory)]);
5256

5357
self.modelName = modelName;
5458
self.storeURL = storeURL;
5559

5660
[NSValueTransformer registerCustomTransformers];
57-
// Create `mainContext` and `writerContext` during initialisation to
58-
// ensure they are only created once.
59-
[self createWriterContext];
60-
[self createMainContext];
61-
[self startListeningToMainContextNotifications];
61+
self.contextFactory = (id<ManagedObjectContextFactory>)[[factory alloc] initWithPersistentContainer:self.persistentContainer];
62+
[[[NullBlogPropertySanitizer alloc] initWithContext:self.contextFactory.mainContext] sanitize];
6263
}
6364

6465
return self;
@@ -81,118 +82,31 @@ + (NSURL *)inMemoryStoreURL
8182

8283
#pragma mark - Contexts
8384

84-
- (NSManagedObjectContext *const)newDerivedContext
85-
{
86-
return [self newChildContextWithConcurrencyType:NSPrivateQueueConcurrencyType];
87-
}
88-
89-
- (void)createWriterContext
85+
- (NSManagedObjectContext *)mainContext
9086
{
91-
NSAssert(self.writerContext == nil, @"%s should only be called once", __PRETTY_FUNCTION__);
92-
93-
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
94-
context.persistentStoreCoordinator = self.persistentStoreCoordinator;
95-
self.writerContext = context;
87+
return self.contextFactory.mainContext;
9688
}
9789

98-
- (void)createMainContext
99-
{
100-
NSAssert(self.mainContext == nil, @"%s should only be called once", __PRETTY_FUNCTION__);
101-
102-
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
103-
context.parentContext = self.writerContext;
104-
self.mainContext = context;
105-
[[[NullBlogPropertySanitizer alloc] initWithContext:context] sanitize];
106-
}
107-
108-
- (NSManagedObjectContext *const)newChildContextWithConcurrencyType:(NSManagedObjectContextConcurrencyType)concurrencyType
90+
- (NSManagedObjectContext *const)newDerivedContext
10991
{
110-
NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc]
111-
initWithConcurrencyType:concurrencyType];
112-
childContext.parentContext = self.mainContext;
113-
childContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
114-
115-
return childContext;
92+
return [self.contextFactory newDerivedContext];
11693
}
11794

118-
11995
#pragma mark - Context Saving and Merging
12096

12197
- (void)saveContextAndWait:(NSManagedObjectContext *)context
12298
{
123-
[self saveContext:context andWait:YES withCompletionBlock:nil];
99+
[self.contextFactory saveContext:context andWait:YES withCompletionBlock:nil];
124100
}
125101

126102
- (void)saveContext:(NSManagedObjectContext *)context
127103
{
128-
[self saveContext:context andWait:NO withCompletionBlock:nil];
104+
[self.contextFactory saveContext:context andWait:NO withCompletionBlock:nil];
129105
}
130106

131107
- (void)saveContext:(NSManagedObjectContext *)context withCompletionBlock:(void (^)(void))completionBlock
132108
{
133-
[self saveContext:context andWait:NO withCompletionBlock:completionBlock];
134-
}
135-
136-
137-
- (void)saveContext:(NSManagedObjectContext *)context andWait:(BOOL)wait withCompletionBlock:(void (^)(void))completionBlock
138-
{
139-
// Save derived contexts a little differently
140-
if (context.parentContext == self.mainContext) {
141-
[self saveDerivedContext:context andWait:wait withCompletionBlock:completionBlock];
142-
return;
143-
}
144-
145-
if (wait) {
146-
[context performBlockAndWait:^{
147-
[self internalSaveContext:context withCompletionBlock:completionBlock];
148-
}];
149-
} else {
150-
[context performBlock:^{
151-
[self internalSaveContext:context withCompletionBlock:completionBlock];
152-
}];
153-
}
154-
}
155-
156-
- (void)saveDerivedContext:(NSManagedObjectContext *)context andWait:(BOOL)wait withCompletionBlock:(void (^)(void))completionBlock
157-
{
158-
if (wait) {
159-
[context performBlockAndWait:^{
160-
[self internalSaveContext:context];
161-
[self saveContext:self.mainContext andWait:wait withCompletionBlock:completionBlock];
162-
}];
163-
} else {
164-
[context performBlock:^{
165-
[self internalSaveContext:context];
166-
[self saveContext:self.mainContext andWait:wait withCompletionBlock:completionBlock];
167-
}];
168-
}
169-
}
170-
171-
- (void)internalSaveContext:(NSManagedObjectContext *)context withCompletionBlock:(void (^)(void))completionBlock
172-
{
173-
[self internalSaveContext:context];
174-
175-
if (completionBlock) {
176-
dispatch_async(dispatch_get_main_queue(), completionBlock);
177-
}
178-
}
179-
180-
- (void)mergeChanges:(NSManagedObjectContext *)context fromContextDidSaveNotification:(NSNotification *)notification
181-
{
182-
[context performBlock:^{
183-
// Fault-in updated objects before a merge to avoid any internal inconsistency errors later.
184-
// Based on old solution referenced here: http://www.mlsite.net/blog/?p=518
185-
NSSet* updates = [notification.userInfo objectForKey:NSUpdatedObjectsKey];
186-
for (NSManagedObject *object in updates) {
187-
NSManagedObject *objectInContext = [context existingObjectWithID:object.objectID error:nil];
188-
if ([objectInContext isFault]) {
189-
// Force a fault-in of the object's key-values
190-
[objectInContext willAccessValueForKey:nil];
191-
}
192-
}
193-
// Continue with the merge
194-
[context mergeChangesFromContextDidSaveNotification:notification];
195-
}];
109+
[self.contextFactory saveContext:context andWait:NO withCompletionBlock:completionBlock];
196110
}
197111

198112
- (void)saveUsingBlock:(void (^)(NSManagedObjectContext *context))aBlock
@@ -211,7 +125,7 @@ - (void)saveUsingBlock:(void (^)(NSManagedObjectContext *context))aBlock complet
211125
[context performBlock:^{
212126
aBlock(context);
213127

214-
[self saveContext:context andWait:NO withCompletionBlock:completion];
128+
[self.contextFactory saveContext:context andWait:NO withCompletionBlock:completion];
215129
}];
216130
}
217131

@@ -294,46 +208,9 @@ - (NSManagedObjectModel *)managedObjectModel
294208
return _managedObjectModel;
295209
}
296210

297-
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
298-
{
299-
return self.persistentContainer.persistentStoreCoordinator;
300-
}
301-
302-
303-
#pragma mark - Notification Helpers
304-
305-
- (void)startListeningToMainContextNotifications
306-
{
307-
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
308-
[nc addObserver:self selector:@selector(mainContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:self.mainContext];
309-
}
310-
311-
- (void)mainContextDidSave:(NSNotification *)notification
312-
{
313-
// Defer I/O to a BG Writer Context. Simperium 4ever!
314-
//
315-
[self.writerContext performBlock:^{
316-
[self internalSaveContext:self.writerContext];
317-
}];
318-
}
319-
320211

321212
#pragma mark - Private Helpers
322213

323-
- (void)internalSaveContext:(NSManagedObjectContext *)context
324-
{
325-
NSParameterAssert(context);
326-
327-
NSError *error;
328-
if (![context obtainPermanentIDsForObjects:context.insertedObjects.allObjects error:&error]) {
329-
DDLogError(@"Error obtaining permanent object IDs for %@, %@", context.insertedObjects.allObjects, error);
330-
}
331-
332-
if ([context hasChanges] && ![context save:&error]) {
333-
[self handleSaveError:error inContext:context];
334-
}
335-
}
336-
337214
- (void)migrateDataModelsIfNecessary:(SentryStartupEvent *)sentryEvent
338215
{
339216
if (![[NSFileManager defaultManager] fileExistsAtPath:[[self storeURL] path]]) {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#import <Foundation/Foundation.h>
2+
#import "ManagedObjectContextFactory.h"
3+
4+
NS_ASSUME_NONNULL_BEGIN
5+
6+
@interface LegacyContextFactory : NSObject <ManagedObjectContextFactory>
7+
8+
@end
9+
10+
NS_ASSUME_NONNULL_END

0 commit comments

Comments
 (0)