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,
Related
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();
}
}];
}];
According to the docs for 0.20 RK:
RKManagedObjectRequestOperation adds special behavior to DELETE requests. Upon retrieving a successful (2xx status code) response for a DELETE, the operation will invoke deleteObject: with the operations targetObject on the managed object context. This will delete the target object from the local store in conjunction the successfully deleted remote representation.
I have been trying to delete an object with such a request but no matter what I try I can't seem to get it to work. I successfully perform a request for many objects which get mapped to appropriate class, and get stored in core data. When I attempt a delete request on one of the objects and get a 200 success back, it does not deleted from local store.
Here's some code where I am no doubt missing a trick.
AppDelegate.m
...
//
// Match Mapping
//
RKEntityMapping *matchMapping = [RKEntityMapping mappingForEntityForName:NSStringFromClass([Match class])
inManagedObjectStore:objectManager.managedObjectStore];
NSDictionary *matchAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
#"objectId", #"id",
#"score", #"matchScore",
#"date", #"matchDate",
nil];
matchMapping.identificationAttributes = #[#"objectId"];
[matchMapping addAttributeMappingsFromDictionary:matchAttributes];
// Response descriptor for GET
[objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:matchMapping
method:RKRequestMethodGET
pathPattern:#"match/"
keyPath:#"matches"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
// Response Descriptor for PUT
[objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:matchMapping
method:RKRequestMethodPUT
pathPattern:#"match/"
keyPath:#"match"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
// Request Descriptor for DELETE
[objectManager addRequestDescriptor:[RKRequestDescriptor requestDescriptorWithMapping:[matchMapping inverseMapping]
objectClass:[Match class]
rootKeyPath:nil
method:RKRequestMethodDELETE]];
MatchDetailVC.m
...
- (void)deleteMatch {
NSDictionary *requiredParameters = #{
#"APIKey": #"xxxxx"
};
NSMutableURLRequest *request = [[RKObjectManager sharedManager] requestWithObject:self.match
method:RKRequestMethodDELETE
path:#"match/"
parameters:requiredParameters];
RKManagedObjectRequestOperation *operation = [[RKObjectManager sharedManager]
managedObjectRequestOperationWithRequest:request
managedObjectContext:[RKManagedObjectStore defaultStore].mainQueueManagedObjectContext
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
//[[RKManagedObjectStore defaultStore].mainQueueManagedObjectContext save:nil]; // IS THIS NEEDED?
NSLog(#"Successfully deleted match.");
[self.navigationController popToRootViewControllerAnimated:YES];
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", [error localizedDescription]);
}];
NSOperationQueue *operationQueue = [NSOperationQueue new];
[operationQueue addOperation:operation];
}
...
Thanks in advance and if you need more code, let me know.
Andy
I know this is quite an old post, but here is what I found out after searching for ages...
The local delete will fail if there is no valid response mapping for the DELETE response.
The problem wen't away for me when I created an empty response mapping like this:
RKObjectMapping* nullMapping = [RKObjectMapping mappingForClass:[NSNull class]];
[objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:nullMapping method:RKRequestMethodDELETE pathPattern:#"mybase/something/:myid/" keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
I am very new to the concept of asynchronous programming, but I get the general gist of it (things get run in the backround).
The issue I'm having is I have a method which utilizes AFNetworking 2.0 to make an HTTP post request, and it works for the most part.
However, I can't figure out how to get the method to actually return the value received from the response as the method returns and THEN gets the value from the response.
-(int) registerUser
{
self.responseValue = 000; //Notice I set this to 000 at start of method
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
NSDictionary *parameters = #{ #"Username": #"SomeUsername" };
[manager POST:#"http://XXX/register"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"JSON: %#", responseObject);
NSError *err = nil;
self.responseValue = [[responseObject objectForKey:#"res"] intValue];
//Note: This^ value returns 99 and NSLogs confirm this
}
failure:^(AFHTTPRequestOperation *operation, NSError *err)
{
NSLog(#"Error: %#", err);
}];
return self.responseValue; //This will return 000 and never 99!
}
Whats the 'proper' way to handle this situation? I've heard whispers of using a 'callback', but I don't really understand how to implement that in this situation.
Any guidance or help would be awesome, cheers!
The issue is that the POST runs asynchronously, as you point out, so you are hitting the return line well before the responseValue property is actually set, because that success block runs later. Put breakpoints/NSLog statements in there, and you'll see you're hitting the return line first.
You generally do not return values from an asynchronous methods, but rather you adopt the completion block pattern. For example:
- (void)registerUserWithCompletion:(void (^)(int responseValue, NSError *error))completion
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
NSDictionary *parameters = #{ #"Username": #"SomeUsername" };
[manager POST:#"http://XXX/register"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"JSON: %#", responseObject);
int responseValue = [[responseObject objectForKey:#"res"] intValue];
if (completion)
completion(responseValue, nil);
}
failure:^(AFHTTPRequestOperation *operation, NSError *err)
{
NSLog(#"Error: %#", err);
if (completion)
completion(-1, err); // I don't know what you want to return if it failed, but handle it appropriately
}];
}
And then, you could use it as follows:
[self registerUserWithCompletion:^(int responseValue, NSError *error) {
if (error)
NSLog(#"%s: registerUserWithCompletion error: %#", __FUNCTION__, error);
else
NSLog(#"%d", responseValue);
// do whatever you want with that responseValue here, inside the block
}];
// Needless to say, don't try to use the `responseValue` here, because
// `registerUserWithCompletion` runs asynchronously, and you will probably
// hit this line of code well before the above block is executed. Anything
// that is dependent upon the registration must called from within the above
// completion block, not here after the block.
Note, I'd suggest you retire that responseValue property you had before, because now that you're using completion blocks, you get it passed back to you via that mechanism, rather than relying on class properties.
Check this one and use search ;-))
Getting variable from the inside of block
its a lot of duplicates already!
:-)
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.
I'm trying to sync my local core data database with a remote JSON API. I'm using RestKit to map JSON values into local managed objects. here is a piece of code:
- (IBAction)testButtonPressed:(id)sender {
NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
NSError *error = nil;
BOOL success = RKEnsureDirectoryExistsAtPath(RKApplicationDataDirectory(), &error);
if (! success) {
RKLogError(#"Failed to create Application Data Directory at path '%#': %#", RKApplicationDataDirectory(), error);
}
// - - - - - - - - Change the path !
NSString *path = [RKApplicationDataDirectory() stringByAppendingPathComponent:#"AC.sqlite"];
NSPersistentStore *persistentStore = [managedObjectStore addSQLitePersistentStoreAtPath:path
fromSeedDatabaseAtPath:nil
withConfiguration:nil
options:nil
error:&error];
if (! persistentStore) {
RKLogError(#"Failed adding persistent store at path '%#': %#", path, error);
}
[managedObjectStore createManagedObjectContexts];
// - - - - - - - - Here we change keys and values
RKEntityMapping *placeMapping = [RKEntityMapping mappingForEntityForName:#"Place"
inManagedObjectStore:managedObjectStore];
[placeMapping addAttributeMappingsFromDictionary:#{
#"place_id": #"place_id",
#"place_title": #"place_title",
#"site": #"site",
#"address": #"address",
#"phone": #"phone",
#"urating": #"urating",
#"worktime": #"worktime",
#"lat": #"lat",
#"lng": #"lng",
#"about": #"about",
#"discount": #"discount",
#"subcategory_title": #"subcategory_title",
#"subcategory_id": #"subcategory_id",
#"category_title": #"category_title",
#"image_url": #"image_url"}];
//RKEntityMapping *articleMapping = [RKEntityMapping mappingForEntityForName:#"Article" inManagedObjectStore:managedObjectStore];
//[articleMapping addAttributeMappingsFromArray:#[#"title", #"author", #"body"]];
//[articleMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"categories" toKeyPath:#"categories" withMapping:categoryMapping]];
NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful); // Anything in 2xx
// here we need to change too
RKResponseDescriptor *responseDescriptor =
[RKResponseDescriptor responseDescriptorWithMapping:placeMapping
pathPattern:nil // #"/articles/:articleID"
keyPath:#"data.place_list"
statusCodes:statusCodes];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://allocentral.api.v1.ladybirdapps.com/place/?access_token=19f2a8d8f31d0649ea19d478e96f9f89b&category_id=1&limit=10"]];
RKManagedObjectRequestOperation *operation = [[RKManagedObjectRequestOperation alloc] initWithRequest:request
responseDescriptors:#[responseDescriptor]];
operation.managedObjectContext = managedObjectStore.mainQueueManagedObjectContext;
operation.managedObjectCache = managedObjectStore.managedObjectCache;
[operation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *result) {
NSLog(#" successfull mapping ");
[self refreshContent];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failed with error: %#", [error localizedDescription]);
}];
NSOperationQueue *operationQueue = [NSOperationQueue new];
[operationQueue addOperation:operation];
}
- (void) refreshContent {
// perform fetch
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
// reload data
[self.tableView reloadData];
}
it works perfect and gets all the objects and stores them in core data, BUT if some objects are deleted on the server, and they are not in the JSON response, they stay in the detebase. how can i make restkit clear out objects that are not in the response? thx
Anytime you receive a new JSON response from your server, you should process it as normal, adding new entries into your Core Data objects.
Then iterate through your Core Data objects, and check to see if they're included in the JSON (using whatever method makes sense for your objects), and if not, delete them.
Alternatively, if you are passing some kind of ID in with the JSON, you could store each ID in an NSArray at the same time as you're adding objects to Core Data. Then do a predicate search for any Core Data objects that don't match the IDs in the array, and delete them.
Which is better depends on whether you have more new/existing items or more to-be-deleted items.