Synchronous calls in an asynchronous environment - ios

I am using AFNetworking 2.0 and Objective-C to create a class which synchronizes the client database with the database on the server.
Of course, the client shouldn't notice when this happens, so the class has to make the calls asynchronous.
However, some calls in this class are dependent on the results of others calls.
Example:
Object: Mammal, id = 15, new id = 26
Subobject: Zebra, clade_id = 15
The zebra can only update its properties once the mammals are done, because it uses some of the mammals properties (id) and if it sends the call too early it results in corrupted data (id = 15 instead of the correct 26).
My question is, how one could use AFNetworking to make these synchronous calls (zebra after mammal has finished) in a way that the user doesn't notice (asynchronous).
Thanks for your answers.

You not need to use a synchronous calls.
1) The first way
// create request with URL
NSMutableURLRequest *request = ....;
// create AFHTTPRequestOperation
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperationManager manager] HTTPRequestOperationWithRequest:request success:^{success block} failure:^{failure block}];
// Call operation
[self.operationQueue addOperation:operation];
In the success block you schedule second operation.
2) The second way
Create both operations at once and set the first operation how dependency for the second operation (but need the additional code for managing shared data).
[operation2 addDependency:operation1];
[self.operationQueue addOperation:operation1];
[self.operationQueue addOperation:operation2];

Related

Send next request when last one is done

I have 100+ request.I need send a new request when the last one is done,so the server will not return error code - 429.
How to make this by afnetworking 3.0?
I'm not very familiar with the specific APIs of AFNetworking, but you could setup:
A variable array containing all your pending requests,
A method called (e.g.) sendNext() that removes the first entry of the array, performs the request asynchronously, and inside the completion block, calls itself.
Of course, you will need a terminating condition, and that is simply stop when the array becomes empty.
There are 2 approaches that can deal with your problem.
Firstly, create an operation queue and add all requests to the queue. After that, create an operation of your new request, then add the dependency to all requests in the queue. As a result, your new operation (will execute the new request) will be performed after the last request is done.
Secondly, you can use dispatch_barrier_async, which will create a synchronized point on your concurrent queue. That means you should create a concurrency queue to execute your 100+ request, and that dispatch_barrier_async blocks in your custom queue will execute the new request.
Thanks Sendoa for the link to the GitHub issue where Mattt explains why this functionality is not working anymore. There is a clear reason why this isn't possible with the new NSURLSession structure; Tasks just aren't operations, so the old way of using dependencies or batches of operations won't work.
I've created this solution using a dispatch_group that makes it possible to batch requests using NSURLSession, here is the (pseudo-)code:
// Create a dispatch group
dispatch_group_t group = dispatch_group_create();
for (int i = 0; i < 10; i++) {
// Enter the group for each request we create
dispatch_group_enter(group);
// Fire the request
[self GET:#"endpoint.json"
parameters:nil
success:^(NSURLSessionDataTask *task, id responseObject) {
// Leave the group as soon as the request succeeded
dispatch_group_leave(group);
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
// Leave the group as soon as the request failed
dispatch_group_leave(group);
}];
}
// Here we wait for all the requests to finish
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// Do whatever you need to do when all requests are finished
});
I want to look write something that makes this easier to do and discuss with Matt if this is something (when implemented nicely) that could be merged into AFNetworking. In my opinion it would be great to do something like this with the library itself. But I have to check when I have some spare time for that.
This question possible duplicate of AFNetworking 3.0 AFHTTPSessionManager using NSOperation . You can follow #Darji comment for few call, For 100+ call add these utility classes https://github.com/robertmryan/AFHTTPSessionOperation/ .
It is very impractical approach to sent 100+ request operation concurrently. If possible try to reduce it.

Does AFHTTPRequestOperationManager run requests on the operationQueue?

If I have a manager created and instance-varred and I do:
AFHTTPRequestOperation *operation = [self.manager HTTPRequestOperationWithRequest:request success:mySuccessBlock failure:myFailureBlock];
[operation start];
will this run on the manager's operationQueue? I can't seem to find anything guaranteeing it will verse using one of the GET, POST, PUT methods instead which I assume will add the operation to the queue.
I see mattt's answer here, but I want to be sure one way or the other.
How send request with AFNetworking 2 in strict sequential order?
What I'm trying to do is queue up requests and have them run synchronously by setting
[self.manager.operationQueue setMaxConcurrentOperationCount:1];
If you want to run an operation on a queue, you have to explicitly add the operation to the queue:
AFHTTPRequestOperation *operation = [self.manager HTTPRequestOperationWithRequest:request success:mySuccessBlock failure:myFailureBlock];
[queue addOperation:operation];
This presumes that you would create a queue:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = #"com.domain.app.networkQueue";
queue.maxConcurrentOperationCount = 1;
If you just start the operations without adding them to a queue (e.g. [operation start]), they'll just run, without honoring any particular queue parameters.
Note, you could also have added your operation to the operation manager's queue (which you can set the maxConcurrentOperationCount, as you alluded to in your question):
[self.manager.operationQueue addOperation:operation];
That saves you from having to create your own queue. But it then presumes that all of your other AFNetworking requests for this manager would also run serially, too. Personally, I'd leave manager.operationQueue alone, and create a dedicated queue for my logic that required serial operations.
One final note: Using serial network operations imposes a significant performance penalty over concurrent requests. I'll assume from your question that you absolutely need to do it serially, but in general I now design my apps to use concurrent requests wherever possible, as it's a much better UX. If I need a particular operation to be serial (e.g. the login), then I do that with operation dependencies or completion blocks, but the rest of the app is running concurrent network requests wherever possible.

RestKit - Process one REST operation at a time

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 NSOperationthat 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];

Stopping an NSOperationQueue

I have an NSOperationQueue that handles importing data from a web server on a loop. It accomplishes this with the following design.
NSURLConnect is wrapped in an NSOperation and added to the Queue
On successful completion of the download (using a block), the data from the request is wrapped in another NSOperation that adds the relevant data to Core Data. This operation is added to the queue.
On successful completion (using another block), (and after a specified delay) I call the method that started it all and return to step 1. Thus, i make another server call x seconds later.
This works great. I'm able to get data from the server and handle everything on the background. And because these are just NSOperations I'm able to put everything in the background, and perform multiple requests at a time. This works really well.
The ONLY problem that I currently have is that I'm unable to successfully cancel the operations once they are going.
I've tried something like the following :
- (void)flushQueue
{
self.isFlushingQueue = YES;
[self.operationQueue cancelAllOperations];
[self.operationQueue waitUntilAllOperationsAreFinished];
self.isFlushingQueue = NO;
NSLog(#"successfully flushed Queue");
}
where self.isFlushingQueue is a BOOL that I use to check before adding any new operations to the queue. This seems like it should work, but in fact it does not. Any ideas on stopping my Frankenstein creation?
Edit (Solved problem, but from a different perspective)
I'm still baffled about why exactly I was unable to cancel these operations (i'd be happy to keep trying possible solutions), but I had a moment of insight on how to solve this problem in a slightly different way. Instead of dealing at all with canceling operations, and waiting til queue is finished, I decided to just have a data structure (NSMutableDictionary) that had a list of all active connections. Something like this :
self.activeConnections = [NSMutableDictionary dictionaryWithDictionary:#{
#"UpdateContacts": #YES,
#"UpdateGroups" : #YES}];
And then before I add any operation to the queue, I simply ask if that particular call is On or Off. I've tested this, and I successfully have finite control over each individual server request that I want to be looping. To turn everything off I can just set all connections to #NO.
There are a couple downsides to this solution (Have to manually manage an additional data structure, and every operation has to start again to see if it's on or off before it terminates).
Edit -- In pursuit of a more accurate solution
I stripped out all code that isn't relevant (notice there is no error handling). I posted two methods. The first is an example of how the request NSOperation is created, and the second is the convenience method for generating the completion block.
Note the completion block generator is called by dozens of different requests similar to the first method.
- (void)updateContactsWithOptions:(NSDictionary*)options
{
//Hard coded for ease of understanding
NSString *contactsURL = #"api/url";
NSDictionary *params = #{#"sortBy" : #"LastName"};
NSMutableURLRequest *request = [self createRequestUsingURLString:contactsURL andParameters:params];
ConnectionCompleteBlock processBlock = [self blockForImportingDataToEntity:#"Contact"
usingSelector:#selector(updateContactsWithOptions:)
withOptions:options andParsingSelector:#selector(requestUsesRowsFromData:)];
BBYConnectionOperation *op = [[BBYConnectionOperation alloc] initWithURLRequest:request
andDelegate:self
andCompletionBlock:processBlock];
//This used to check using self.isFlushingQueue
if ([[self.activeConnections objectForKey:#"UpdateContacts"] isEqualToNumber:#YES]){
[self.operationQueue addOperation:op];
}
}
- (ConnectionCompleteBlock) blockForImportingDataToEntity:(NSString*)entityName usingSelector:(SEL)loopSelector withOptions:(NSDictionary*)options andParsingSelector:(SEL)parseSelector
{
return ^(BOOL success, NSData *connectionData, NSError *error){
//Pull out variables from options
BOOL doesLoop = [[options valueForKey:#"doesLoop"] boolValue];
NSTimeInterval timeInterval = [[options valueForKey:#"interval"] integerValue];
//Data processed before importing to core data
NSData *dataToImport = [self performSelector:parseSelector withObject:connectionData];
BBYImportToCoreDataOperation *importOperation = [[BBYImportToCoreDataOperation alloc] initWithData:dataToImport
andContext:self.managedObjectContext
andNameOfEntityToImport:entityName];
[importOperation setCompletionBlock:^ (BOOL success, NSError *error){
if(success){
NSLog(#"Import %#s was successful",entityName);
if(doesLoop == YES){
dispatch_async(dispatch_get_main_queue(), ^{
[self performSelector:loopSelector withObject:options afterDelay:timeInterval];
});
}
}
}];
[self.operationQueue addOperation:importOperation];
};
}
Cancellation of an NSOperation is just a request, a flag that is set in NSOperation. It's up to your NSOperation subclass to actually action that request and cancel it's work. You then need to ensure you have set the correct flags for isExecuting and isFinished etc. You will also need to do this in a KVO compliant manner. Only once these flags are set is the operation finished.
There is an example in the documentation Concurrency Programming Guide -> Configuring Operations for Concurrent Execution. Although I understand that this example may not correctly account for all multi-threaded edge cases. Another more complex example is provided in the sample code LinkedImageFetcher : QRunLoopOperation
If you think you are responding to the cancellation request correctly then you really need to post your NSOperation subclass code to examine the problem any further.
Instead of using your own flag for when it is ok to add more operations, you could try the
- (void)setSuspended:(BOOL)suspend
method on NSOperationQueue? And before adding a new operation, check if the queue is suspended with isSuspended?

Queuing NSURLRequest to simulate a synchronous, blocking request

I am interacting with a web-controlled hardware device. You send it a request via a URL (e.g., http://device/on?port=1 or http://device/off?port=3) to turn stuff on and off, and it sends back "success" or "failure". It is a simple device, however, so while it's processing a request --- i.e., until it returns the status of the request that it's processing --- it will ignore all subsequent requests. It does not queue them up; they just get lost.
So I need to send serial, synchronous requests. I.e., req#1, wait for response#1, req#2, wait for response#2, req#3, wait for response #3, etc.
Do I need to manage my own thread-safe queue of requests, have the UI thread push requests into one end of the queue, and have another thread pull the requests off, one at a time, as soon as the previous one either completes or times out, and send the results back to the UI thread? Or am I missing something in the API that already does this?
Thanks!
...R
What should work is to use an NSOperationQueue instance, and a number of NSOperation instances that perform the various URL requests.
First, set up a queue in the class that will be enqueueing the requests. Make sure to keep a strong reference to it, i.e.
#interface MyEnqueingClass ()
#property (nonatomic, strong) NSOperationQueue *operationQueue;
#end
Somewhere in the implementation, say the init method:
_operationQueue = [[NSOperationQueue alloc] init];
_operationQueue.maxConcurrentOperationCount = 1;
You want basically a serial queue, hence the maxConcurrentOperationCount of 1.
After setting this up, you'll want to write some code like this:
[self.operationQueue addOperationWithBlock:^{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"my://URLString"]];
NSError *error;
NSURLResponse *response;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (!responseData)
{
//Maybe try this request again instead of completely restarting? Depends on your application.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
//Do something here to handle the error - maybe you need to cancel all the enqueued operations and start again?
[self.operationQueue cancelAllOperations];
[self startOver];
}];
}
else
{
//Handle the success case;
}
}];
[self.operationQueue addOperationWithBlock:^{
//Make another request, according to the next instuctions?
}];
In this way you send synchronous NSURLRequests and can handle the error conditions, including by bailing out completely and starting all over (the lines with -cancelAllOperations called). These requests will be executed one after the other.
You can also of course write custom NSOperation subclasses and enqueuing instances of those rather than using blocks, if that serves you.
Hope this helps, let me know if you have any questions!
You can use NSOperationQueue class and also use some API which are build in on it for example AFNetworking.

Resources