I'm using RestKit 0.20.3 and have some REST operations that needs to be done in a certain order (the response from one REST operation needs to be included in the request parameter mapping for the next).
I tried setting up the queue to handle one operation at a time like this:
RKObjectManager.sharedManager.operationQueue.maxConcurrentOperationCount = 1;
And adding the operations like this:
for (id insertedObject in insertedObjects) {
[RKObjectManager.sharedManager postObject:insertedObject path:nil parameters:nil success:nil failure:nil];
}
But I get an error, because the first operation is not fully completed before the other start.
When inspecting the logs, it seems like it is executed like this:
- REST operation 1 - Request mapping
- REST operation 2 - Request mapping
- REST operation 3 - Request mapping
- REST operation 1 - HTTP call and response mapping
- REST operation 2 - HTTP call and response mapping
- REST operation 3 - HTTP call and response mapping
I have already tried setting operation dependencies, but that does not make a difference.
I need one REST operation to be completed at a time. How do I do this in RestKit?
PROBLEM
RestKit uses multiple NSOperation
for one REST operation, so all request mappings will be queued first with the code in the question. So when the first request mapping is executed and queuing the actual HTTP request, it gets queued behind the first two request mapping operations.
SOLUTION
Queue the next operation after the first one finishes.
Example with recursion:
- (void)sync {
NSArray *objectsToPostInOrder = ...;
for (id objectToPost in objectsToPostInOrder) {
[RKObjectManager.sharedManager postObject:objectToPost path:nil parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
// Proceed with next if everything went OK
[self sync];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
// Don't continue posting as they are dependent on each other
[MyHUD showErrorWithStatus:error.localizedDescription];
}];
return;
}
}
}
I managed to make it work by defining a custom asynchronous NSOperation, which uses RestKit's object get and does not indicate it is finished before RestKit's success/failure block is executed. These custom operations are then added into a separate queue (not RestKit's operation's queue) with maxConcurrentOperationCount set to 1, or you can define inter-operation dependencies as you want.
An older question, but a common recurring problem:
This kind of asynchronous problem can be easily solved with a "Promise" (please read more about here in this wiki:Futures and Promises for a general instruction).
A "Promise" represents the eventual result of an asynchronous method. Eventual it becomes either the value you waiting for or an error.
You can "chain" such asynchronous methods in a way that guarantees that the "next" method is only called when the first has been finished successfully. And, there's also a way to "catch" errors that might have been "thrown" from an asynchronous method.
First, you need to wrap your NSOperation
that invokes the request into an asynchronous method that returns a promise:
- (Promise*) performRequestWithParams(NSDictionary* params);
Note, this is an asynchronous method. It returns immediately returning a "pending" promise. The promise is "resolved" eventually when the operation finishes by the NSOperation
which you have to implement in the wrapper method.
Now, in order to "continue" with the next operation when the first finishes, you define a continuation using then
as shown below:
Promise* promise = [self performRequestWithParams:params];
promise.then(^id(NSArray* result) {
// We enter here, when the 1. REST op finished successfully:
// note: here, `result` is the _result_ of the async operation above
// which was a JSON returned from the service and parsed into an array.
// Obtain a value from the result:
id x = result[0][@"someKey"];
// Create a new JSON representation which is the input for the next REST operation:
NSDictionary* params = ...;
// Now, invoke the 2. REST op asynchronously:
return [self performRequestWithParams:params];
}, nil)
.then(^id(NSArray* result) {
// We enter here, when the 2. REST op finished successfully:
id x = result[0][@"someKey"]; // obtain a value from some dictionary
NSDictionary* params = ...; // create a new JSON
// Now, invoke the next async method:
return [self performRequestWithParams:params];
}, nil)
... // 3., 4., ... add more REST operations
// In case of an error in *any* of the operations above, let us "catch" it here:
.then(nil, ^id(NSError* error){
NSLog(@"Error: %@", error);
});
Note: you can use a NSOperationQueue
to control how many requests your program should execute concurrently. The continuations defined with the promise above are orthogonal to the constraints of the NSOperationQueue
. That is, you can use the same NSOperationQueue
which is configured to execute for example four concurrent operations, and which executes any other unrelated REST operation in parallel without disrupting the control flow of the "serialized" continuations above.
There are a couple of Promise libraries around. I'm the author of one of it. The project is open source available on GitHub: RXPromise.
Works well for me so far.
AFRKHTTPClient *client = [[AFRKHTTPClient alloc] initWithBaseURL:self.baseUrl];
client.operationQueue.maxConcurrentOperationCount = 1;
RKObjectManager *objectManager = [[RKObjectManager alloc] initWithHTTPClient:client];
objectManager.operationQueue.maxConcurrentOperationCount = 1;
[RKObjectManager setSharedManager:objectManager];