Source

CDOperation / CDOperation.m

Full commit
//
//  CDOperation.m
//
//  Created by Dmitry Makarenko on 05.06.11.
//  Copyright 2011 Dmitry Makarenko. All rights reserved.
//

#import "CDOperation.h"

@interface NSManagedObjectContext (Additions)

- (BOOL) save;

@end

@implementation NSManagedObjectContext (Additions)

- (BOOL) save {
    NSError *error = nil;
    BOOL status = YES;
    if ([self hasChanges]) {
        if (![self save:&error]) {
            NSLog(@"Unresolved context save error: %@, %@", error, [error userInfo]);
            status = NO;
        }
    }
    return status;
}

@end


@interface CDOperation ()

@property (nonatomic, assign) NSThread* initialThread;
@property (nonatomic, retain) NSManagedObjectContext* mainManagedObjectContext;
@property (nonatomic, retain) NSManagedObjectContext* operationManagedObjectContext;

@property (nonatomic, retain) NSMutableArray* changeNotifications;

- (void) processMainContextChangeNotification:(NSNotification*)notification;
- (void) mergeMainContextChangesIntoOperationContext;

- (void) processOperationContextChangeNotification:(NSNotification*)notification;
- (void) mergeOperationContextChangesIntoMainContext:(NSNotification*)notification;

- (void) doMain;

@end

@implementation CDOperation

@synthesize initialThread = initialThread_;
@synthesize mainManagedObjectContext = mainManagedObjectContext_;
@synthesize operationManagedObjectContext = operationManagedObjectContext_;
@synthesize changeNotifications = changeNotifications_;

- (void) processMainContextChangeNotification:(NSNotification*)notification {
    @synchronized(self) {
        [self.changeNotifications addObject:notification];
    }
}

- (void) mergeMainContextChangesIntoOperationContext {
    @synchronized(self) {
        if ([changeNotifications_ count] > 0) {
            NSLog(@"Merging changes from MAIN context to OPERATION context started");
            for(NSNotification* change in self.changeNotifications) {
                [operationManagedObjectContext_ mergeChangesFromContextDidSaveNotification:change];
            }
            [operationManagedObjectContext_ save];
            [self.changeNotifications removeAllObjects];
            NSLog(@"Merging changes from MAIN context to OPERATION context finished");
        }
    }
}

- (void) processOperationContextChangeNotification:(NSNotification*)notification {
    [self performSelector:@selector(mergeOperationContextChangesIntoMainContext:)
                 onThread:self.initialThread
               withObject:notification
            waitUntilDone:NO];
}

- (void) mergeOperationContextChangesIntoMainContext:(NSNotification*)notification {
    NSLog(@"Merging changes from OPERATION context to MAIN context started");
    [mainManagedObjectContext_ mergeChangesFromContextDidSaveNotification:notification];
    [mainManagedObjectContext_ save];
    NSLog(@"Merging changes from OPERATION context to MAIN context finished");
}

- (id) initWithMainManagedObjectContext:(NSManagedObjectContext*)context {
    if ((self = [super init])) {
        self.initialThread = [NSThread currentThread];
        self.mainManagedObjectContext = context;
        self.operationManagedObjectContext = nil;
        self.changeNotifications = [NSMutableArray arrayWithCapacity:4];

        // Observing main thread's (or thread's, where that operation was created) NSMangedObjectContext save notification.
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(processMainContextChangeNotification:)
                                                     name:NSManagedObjectContextDidSaveNotification
                                                   object:mainManagedObjectContext_];
 
    }
    return self;
}

- (void) main {
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    @try {
        if (![self isCancelled]) {

            self.operationManagedObjectContext = [[[NSManagedObjectContext alloc] init] autorelease];
            [operationManagedObjectContext_ setPersistentStoreCoordinator:[mainManagedObjectContext_ persistentStoreCoordinator]];

             // Observing operation's thread NSMangedObjectContext save notification.
            [[NSNotificationCenter defaultCenter] addObserver:self
                                                     selector:@selector(processOperationContextChangeNotification:)
                                                         name:NSManagedObjectContextDidSaveNotification
                                                       object:operationManagedObjectContext_];
            
            [self doMain];
            
            [self mergeMainContextChangesIntoOperationContext];
        }
    }
    @catch(NSException* exception) {
        NSLog(@"Exception: %@, %@", exception.name, exception.reason);
    }
    @catch(...) {
        NSLog(@"%@", @"Not NSException-based exception!");
    }
    @finally {
        [[NSNotificationCenter defaultCenter] removeObserver:self];
        self.operationManagedObjectContext = nil;
        [pool release];
    }
}

- (void) doMain {
    NSAssert(NO, @"Method has to be overriden in subclasses!");
}

#pragma mark Memory management

- (void) dealloc {
    self.initialThread = nil;
    self.mainManagedObjectContext = nil;
    self.changeNotifications = nil;
    
    [super dealloc];
}

@end