Coredata CRUD operations in background thread? - objective-c

I am using the following code snippets from apple for Create/Update NSManagedObject in my application
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Add code here to do background processing
//
//
NSManagedObjectContext *private = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[private setParentContext:mainMoc];
[private performBlock:^{
for (NSDictionary *jsonObject in jsonArray) {
NSManagedObject *mo = …; //Managed object that matches the incoming JSON structure
//update MO with data from the dictionary
}
NSError *error = nil;
if (![private save:&error]) {
NSLog(#"Error saving context: %#\n%#", [error localizedDescription], [error userInfo]);
abort();
}
[mainMoc performBlockAndWait:^{
NSError *error = nil;
if (![mainMoc save:&error]) {
NSLog(#"Error saving context: %#\n%#", [error localizedDescription], [error userInfo]);
abort();
}
}];
}];
dispatch_async( dispatch_get_main_queue(), ^{
// Add code here to update the UI/send notifications based on the
// results of the background processing
});
});
I have two doubts
For just reading the values from my model using the above code,
[private performBlock:^{}); is required ?
Is there any better approach for Create/Update opertaions in background thread. Am I using the best approach for mentioned operations ?
Thanks in advance

From Apple's Documentations Concurrency
performBlock: and performBlockAndWait: ensure the block operations are executed on the queue specified for the context. The performBlock: method returns immediately and the context executes the block methods on its own thread. With the performBlockAndWait: method, the context still executes the block methods on its own thread, but the method doesn’t return until the block is executed.
When you use NSPrivateQueueConcurrencyType, the context creates and manages a private queue.
So you do not need to create another dispatch queue if you use performBlock: because it is asynchronously executes operations within block. Otherwise, if you use performBlockAndWait: which wait until to finish it's operation execution and in this case you should use another dispatch queue.
Therefore, best practice is to avoid the use of another dispatch queue. e.g
NSManagedObjectContext *private = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[private setParentContext:mainMoc];
[private performBlock:^{
for (NSDictionary *jsonObject in jsonArray) {
NSManagedObject *mo = …; //Managed object that matches the incoming JSON structure
//update MO with data from the dictionary
}
NSError *error = nil;
if (![private save:&error]) {
NSLog(#"Error saving context: %#\n%#", [error localizedDescription], [error userInfo]);
abort();
}
[mainMoc performBlockAndWait:^{
NSError *error = nil;
if (![mainMoc save:&error]) {
NSLog(#"Error saving context: %#\n%#", [error localizedDescription], [error userInfo]);
abort();
}
}];
}];

Related

What happens with unmatched dispatch_group_enter and dispatch_group_leave?

I have an async NSOperation to download data for multiple ImageFile objects. Since this is all happening asynchronously I am using a dispatch group to track the requests and then dispatch_group_notify to complete the operation when they are all done.
The question I have is what happen when the operation is ended prematurely, either by cancelation or by some other error. The dispatch group will be left with unmatched dispatch_group_enter and dispatch_group_leave so dispatch_group_notify will never be called. Is it the block retained somewhere by the system forever waiting, or will it get released when the NSOperation gets released?
Or is my approach not ideal, how else should I do this?
- (void)main
{
if (self.cancelled) {
[self completeOperation];
return;
}
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
context.persistentStoreCoordinator = self.persistentStoreCoordinator;
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
[context performBlock:^{
NSFetchRequest *request = [ImageFile fetchRequest];
request.predicate = ....
request.sortDescriptors = ...
NSError *error;
NSArray *imageFiles = [context executeFetchRequest:request error:&error];
if (!imageFiles) {
// Error handling...
[self completeOperation];
return;
}
dispatch_group_t group = dispatch_group_create();
for (ImageFile *imageFile in imageFiles) {
dispatch_group_enter(group);
#autoreleasepool {
[self.webService requestImageWithId:imageFile.id completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (self.cancelled) {
[self completeOperation];
return;
}
[context performBlock:^{
if (data && !error) {
imageFile.data = data;
NSError *error;
if (![context save:&error]) {
// Error handling...
[self completeOperation];
return;
}
}
dispatch_group_leave(group);
}];
}];
}
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[self completeOperation];
});
}];
}
From the docs for dispatch_group_enter():
A call to this function must be balanced with a call to dispatch_group_leave.
From the docs for dispatch_group_t:
The dispatch group keeps track of how many blocks are outstanding, and GCD retains the group until all its associated blocks complete execution.
It talks about outstanding blocks, but what it really means is unmatched calls to dispatch_group_enter().
So, the answer to your question about what happens is that the dispatch group object effectively leaks. The block object passed to dispatch_group_notify() and any objects it has strong references to also leak. In your case, that includes self.
The answer to your question of whether your approach is "ideal" is: no, it's not ideal. It's not even valid by the design contract of GCD. You must balance all calls to dispatch_group_enter() with calls to dispatch_group_leave().
If you want to somehow distinguish between success and failure or normal completion and cancellation, you should set some state that's available to the notify block and then code the notify block to consult that state to decide what to do.
In your case, though, the code paths where you fail to call dispatch_group_leave() just do the same thing the notify block would do. So I'm not even sure why you're not just calling dispatch_group_leave() rather than calling [self completeOperation] in those cases.

RESTKit 0.20 Operations queue

I am trying to implement RESTKit 0.20 operations queue, I have read on the blogs that NSOperationQueue may also be used to create queue of operations. I wants to use native approach of RestKit operations queue.
Can any one please post the piece of code/example with followings:
How to use Operations queue in RestKit.
Setting queue to execute one operation at a time.
If the first operation is not completed then I need to cancel pending operations in this queue.
Looks forward to hear form you.
Thanks.
Here I am sharing you piece of Code I am using for ManagedObjects(CoreData Objects) Request Operations.
Get references to objectManager & managedObjectContext;
RKObjectManager *objectManager = [(AppDelegate *)[[UIApplication sharedApplication] delegate] objectManager];
NSManagedObjectContext *managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
Initialise array to hold up operations in it
NSMutableArray *requestOperations = [NSMutableArray array];
Prepare first Operation and add it to requestOperations array, notice failure block is cancelling pending operations in queue.
// Setup Organization Operation
//
NSString *url = #"organizations/syncAll/";
NSMutableURLRequest *organizationsRequest = [objectManager requestWithObject:organizations method:RKRequestMethodPOST path:url parameters:nil];
RKObjectRequestOperation *organizationsOperation = [objectManager managedObjectRequestOperationWithRequest:organizationsRequest managedObjectContext:managedObjectContext success: ^(RKObjectRequestOperation *operation, RKMappingResult *result) {
..
[RKUtils isHandleStatusError:[result array]];
} failure: ^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failed with error: %#", [error localizedDescription]);
[objectManager cancelAllObjectRequestOperationsWithMethod:RKRequestMethodPOST matchingPathPattern:#"games/getNotStartedGames"];
[RKUtils handleError:error];
}];
[requestOperations addObject:organizationsOperation];
prepare second operation
// Setup Games Operation
//
url = #"games/syncAll/";
NSMutableURLRequest *gamesRequest = [objectManager requestWithObject:games method:RKRequestMethodPOST path:url parameters:nil];
RKObjectRequestOperation *gamesOperation = [objectManager managedObjectRequestOperationWithRequest:gamesRequest managedObjectContext:managedObjectContext success: ^(RKObjectRequestOperation *operation, RKMappingResult *result) {
..
[RKUtils isHandleStatusError:[result array]];
} failure: ^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failed with error: %#", [error localizedDescription]);
if (error.code == NSURLErrorCancelled) {
return;
}
[RKUtils handleError:error];
}];
[requestOperations addObject:gamesOperation];
prepare more operations
..
Set max concurrent operations count to 1
objectManager.operationQueue.maxConcurrentOperationCount = 1;
Enqueue all operations in queue. The queue will start executing operations one by one.
// Enqueue Request Operations
[objectManager enqueueBatchOfObjectRequestOperations:requestOperations progress: ^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
NSLog(#"totalNumberOfOperations : %d", totalNumberOfOperations);
NSLog(#"numberOfFinishedOperations : %d", numberOfFinishedOperations);
} completion: ^(NSArray *operations) {
NSLog(#"completion");
}];
Hope that gonna deliver your purpose.
Cheers,

IOS How to sync multithreading NSManagedObjectContext?

Application must update data from WebService in loop each 10 sec in background and display data to user by his request in the main thread. Also I need update and delete records by user request.
Updates done with runloop.
I have registered notification in the AppDelegate
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:nil];
- (void)contextChanged:(NSNotification*)notification
{
if ([notification object] == [self managedObjectContext]) return;
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(contextChanged:) withObject:notification waitUntilDone:YES];
return;
}
[[self managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
[self saveContext]; //do I need this here or marge save data too?
}
I have Storage sharedInstance class with
NSOperationQueue* operationQueue;
then inserts,updates,selects operators added this way from any thread:
-(void)do_something
{
[self.operationQueue addOperationWithBlock:^{
NSManagedObjectContext*moc; //creating new NSManagedObjectContext with AppDelegate.persistentStoreCoordinator
//do my staff
[moc save:&error]
}]
}
The problem is when I try update entities with #"my_id=%#", #(my_id)
[moc countForFetchRequest:fetchRequest error:&error]
return 0 and cause inserting of duplicate exists entity
The problem is with synchronization.
Advice please.
should I use instance of dispatch_queue_create("com.my.", 0); instead for each CoreData operation?
I did try remove operationQuiue
-(void)query:(void(^)(NSManagedObjectContext *context))queryBlock
{
NSLog(#"query CALL");
__block NSManagedObjectContext *context;
//if remove dispatch_sync and/or run in main thread result the same
dispatch_sync( dispatch_queue_create("com.myapp.db-queue", 0), ^{
AppDelegate*app = AppDelegate();
//same result if I use
//app.persistentStoreCoordinator or
//[app.managedObjectContext persistentStoreCoordinator]
NSPersistentStoreCoordinator *persistentStoreCoordinator= [app.managedObjectContext persistentStoreCoordinator];
context = [NSManagedObjectContext new];
[context setPersistentStoreCoordinator:persistentStoreCoordinator];
[context setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
queryBlock(context);
if ([context hasChanges])
{
NSError*err;
[context save:&err];
if (err) {
NSLog(#"context save: %#",[err localizedDescription]);
}
}
});
}
and call it as :
CoreStorage* cs = [CoreStorage sharedInstance];
NSArray* list = [ws GetSections]; //array of NSDictionaries
//if add this to operationQuiue resunt the same
[cs query:^(NSManagedObjectContext *moc) {
NSLog(#"START");
for (NSDictionary *section in list) {
NSNumber* Id= #([[section objectForKey:#"section_id"] integerValue]);
NSFetchRequest * fetchRequest = [NSFetchRequest new];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Section" inManagedObjectContext: moc];
[fetchRequest setEntity:entity];
[fetchRequest setFetchLimit:1];
[fetchRequest setIncludesSubentities:NO];
[fetchRequest setPredicate: [NSPredicate predicateWithFormat:#"section_id=%#",Id]];
NSError *error =nil;
Section *entry;
if ([moc countForFetchRequest:fetchRequest error:&error] >0)
{
entry = [moc executeFetchRequest:fetchRequest error:nil][0];
NSLog(#"exist"); //this never call
}
else
{
entry = [NSEntityDescription insertNewObjectForEntityForName:#"Section" inManagedObjectContext:moc];
NSLog(#"NEW");
}
entry.section_id = Id;
entry.timeStamp = [NSDate date];
}
}];
Any sugastions please?
The problem is probably in the operation queue. You haven't configured it with max concurrent operations to be 1, right? In this case it is not serial, and operations that you add to it run concurrently. So here what happens. First operation fetches for the count of object with some ID, doesn't find it and creates one. At some point before it saves, another operation is added. This second operation fetches for the object with the same ID, doesn't find it and creates one. Then the first operation saves, then the second one saves, and you have a duplicate.
So try to make your operation queue serial [operationQueue maxConcurrentOperationCount:1];.
And no, you don't have to save after calling merge method of the managed object context.

Threads Using Managed Objects Between Contexts

I am at that point where I am losing hair on this so I figured I'd reach out to the great minds here who have had experience using Objective C with Threads and core data. I am having issues with managed objects inserted in on thread in a NSPrivateQueue Context being accessed from the main thread. So at a high level I am using AFNetworking to generate a thread to make requests to retrieve JSON data from a server and then insert the values into my persistent store core data. After this is done I have another thread for downloading some binary data using AFNetworking as well. I have set up 2 managed contexts for this as shown below:
(NSManagedObjectContext *)masterManagedContext {
if (_masterManagedContext != nil) {
return _masterManagedContext;
}
NSPersistentStoreCoordinator *coord = [self coordinator];
if (coord != nil) {
_masterManagedContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_masterManagedContext.stalenessInterval = 0.0;
[_masterManagedContext performBlockAndWait:^{
[_masterManagedContext setPersistentStoreCoordinator:coord];
}];
}
return _masterManagedContext;
}
// Return the NSManagedObjectContext to be used in the background during sync
- (NSManagedObjectContext *)backgroundManagedContext {
if (_backgroundManagedContext != nil) {
return _backgroundManagedContext;
}
NSManagedObjectContext *masterContext = [self masterManagedContext];
if (masterContext != nil) {
_backgroundManagedContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_backgroundManagedContext.stalenessInterval = 0.0;
[_backgroundManagedContext performBlockAndWait:^{
[_backgroundManagedContext setParentContext:masterContext];
}];
}
return _backgroundManagedContext;
}
As is shown above I am using a child context and the parent context. When I make I call to fetch the json data I have something like below:
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
//Initially delete all records in table. This will change
[[Singleton sharedInstance]removeEntityObjects:className];
for (int x=0; x < [JSON count]; x++) {
NSMutableDictionary *curDict = [JSON objectAtIndex:x];
[[CoreDatam sharedinstance] insertEmployeesWithDictionary:curDict];
}else {
/* do nothing */
}
}
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error,id JSON) {
[delegate performSelector:#selector(didNotCompleteSync:) withObject:className];
}];
[operations addObject:operation];
}
[self.AFClient enqueueBatchOfHTTPRequestOperations:operations progressBlock:^(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations) {
NSLog(#"Currenlty downloaded table data %d of %d!",numberOfCompletedOperations,totalNumberOfOperations);
} completionBlock:^(NSArray *operations) {
if (_syncInProgress) {
[[CoreDatam sharedInstance]updateEmpForId];
[self downloadAllFiles];
}
}];
}`
for the insert function I have something like below:
insertEmployeesWithDictionary:curDict {
[[self backgroundManagedContext]performBlockAndWait:^{
Employee *emp = [NSEntityDescription insertNewObjectForEntityForName:#"Employee"
inManagedObjectContext:[self backgroundManagedContext]];
/* Issues saving null into core data based on type.*/
[emp setFirst:[dictCopy objectForKey:#"first"]];
[emp setLast:[dictCopy objectForKey:#"last"]];
NSError *error = nil;
BOOL saved;
saved = [[self backgroundManagedContext] save:&error];
if (!saved) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
[self saveMasterContext];
}];
}
The issue is below where I am trying to access the managed objects in the method that is in the completion block above:
updateEmpId {
[self.backgroundManagedContext performBlockAndWait:^{
NSError *error = nil;
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"Employee"];
[request setSortDescriptors:[NSArray arrayWithObject:
[NSSortDescriptor sortDescriptorWithKey:#"last" ascending:YES]]];
myEmps = [self.backgroundManagedContext executeFetchRequest:request error:nil];
for (Employee *moEmp in myEmps) {
[[self backgroundManagedContext]refreshObject:moEmp mergeChanges:YES];
moEmp.photo = #'default.pic';
}
NSError *saveError = nil;
if (![self.backgroundManagedContext save:&saveError]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
[self saveMasterContext];
}
The issue is that I am getting very inconsistent behavior when looking at the managed objects that are modified in the main thread. Is it still necessary to pass managed objectIds when using a parent child context relation? if so how can I do so for the above example? Any help greatly appreciated.
You should pass NSManagedObjectIDs or re-fetch in the main thread context, yeah. If you pass object IDs, get the IDs from the background context after saving the new Employee objects, and use existingObjectWithID:error: in the parent context to instantiate them there. Or just re-do the fetch request from your updateEmpId code block in the masterManagedContext.

QTCaptureOutput.delegate captureOutput:didOutputVideoFrame:... never called

Source
So, I have a QTCaptureSession set up thusly:
//Setup Camera
cameraSession = [[QTCaptureSession alloc] init];
QTCaptureDevice *camera = [QTCaptureDevice deviceWithUniqueID: cameraID];
BOOL success = [camera open: &error];
if (!success || error)
{
NSLog(#"Could not open device %#.", cameraID);
NSLog(#"Error: %#", [error localizedDescription]);
return nil;
}
//Setup Input Session
QTCaptureDeviceInput *cameraInput = [[QTCaptureDeviceInput alloc] initWithDevice: camera];
success = [cameraSession addInput: cameraInput error: &error];
if (!success || error)
{
NSLog(#"Could not initialize input session.");
NSLog(#"Error: %#", [error localizedDescription]);
return nil;
}
//Setup Output
QTCaptureDecompressedVideoOutput *cameraOutput = [[QTCaptureDecompressedVideoOutput alloc] init];
[cameraOutput setDelegate: self];
success = [cameraSession addOutput: cameraOutput error: &error];
if (!success || error)
{
NSLog(#"Could not initialize output session.");
NSLog(#"Error: %#", [error localizedDescription]);
return nil;
}
And the QTCaptureDecompressedVideoOutput delegate's captureOutput:didOutputVideoFrame:WithSampleBuffer:fromConnection: thusly:
- (void)captureOutput:(QTCaptureOutput *)captureOutput didOutputVideoFrame:(CVImageBufferRef)videoFrame withSampleBuffer:(QTSampleBuffer *)sampleBuffer fromConnection:(QTCaptureConnection *)connection
{
NSLog(#"starting convert\n");
}
I then start the capture processing using:
[cameraSession startRunning];
All of the variables initialize fine, and the session starts fine, but captureOutput:didOutputVideoFrame:withSampleBuffer:fromConnection: never gets called.
Context
This is a command-line app, compiled with the GCC. It's linked against the following frameworks:
Foundation
Cocoa
QTKit
QuartzCore
Relevant Miscellany
The frame is not likely dropping because captureOutput:didDropVideoFrameWithSampleBuffer:fromConnection: is also not getting called.
So, with some help from Mike Ash, I managed to figure out that my program was terminating immediately and not waiting for the delegate callback (which, according to Apple's QTKit docs, might occur on a separate thread).
My solution was to add a BOOL properties to my object named captureIsFinished, then add this to the main() function:
//Wait Until Capture is Finished
while (![snap captureIsFinished])
{
[[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1]];
}
Which effectively perpetuates the run-loop of the app for 1 second, checks to see if the capture is finished, then runs for another second.