Tracking Asynchronous Connections with Blocks - ios

I am doing a lot of URL requests (about 60 small images) and I have started to do them Asynchronously. My code adds another image (little downloading thing) and then sets a Request going.
When the request is done I want "data" to be put in the location which was originally added for it, however, I can not see how to pass "imageLocation" to the block for it to store the image in the correct location.
I have replaced the 3rd line with below which seems to work but I am not 100% it is correct (it is very hard to tell as the images are nearly identical). I am also thinking that it is possible to pass "imageLocation" at the point where the block is declared.
Can any confirm any of this?
__block int imageLocation = [allImages count] - 1;
// Add another image to MArray
[allImages addObject:[UIImage imageNamed:#"downloading.png"]];
imageLocation = [allImages count] - 1;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
[request setTimeoutInterval: 10.0];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue currentQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (data != nil && error == nil)
{
//All Worked
[allImages replaceObjectAtIndex:imageLocation withObject:[UIImage imageWithData:data]];
}
else
{
// There was an error, alert the user
[allImages replaceObjectAtIndex:imageLocation withObject:[UIImage imageNamed:#"error.png"]];
}];

Dealing with asynchronous methods is a pain ;)
In your case its guaranteed that the completion block will execute on the specified queue. However, you need to ensure that the queue has a max concurrent operations count of 1, otherwise concurrent access to shared resources is not safe. That's a classic race http://en.wikipedia.org/wiki/Race_condition. The max concurrent operations of a NSOperationQueue can be set with a property.
In general, completion handlers may execute on any thread, unless otherwise specified.
Dealing with asynchronous methods gets a lot easier when using a concept called "Promises" http://en.wikipedia.org/wiki/Promise_(programming). Basically, "Promises" represent a result that will be evaluated in the future - nonetheless the promise itself is immediately available. Similar concepts are named "futures" or "deferred".
There is an implementation of a promise in Objective-C on GitHub: RXPromise. When using it you also get safe access from within the handler blocks to shared resources. An implementation would look as follows:
-(RXPromise*) fetchImageFromURL:(NSString*)urlString queue:(NSOperationQueue*) queue
{
#autoreleasepool {
RXPromise* promise = [[RXPromise alloc] init];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
[NSURLConnection sendAsynchronousRequest:request
queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (data != nil) {
[promise fulfillWithValue:data];
}
else { // There was an error
[promise rejectWithReason:error];
};
}];
return promise;
}
}
Then call it:
- (void) fetchImages {
...
for (NSUInteger index = 0; index < N; ++index)
{
NSString* urlString = ...
[self fetchImageFromURL:urlString, self.queue]
.then(^id(id data){
[self.allImages replaceObjectAtIndex:index withObject:[UIImage imageWithData:data]];
return #"OK";
},
^id(NSError* error) {
[self.allImages replaceObjectAtIndex:index withObject:[UIImage imageNamed:#"error.png"]];
return error;
});
}
}

A couple of thoughts:
If you want to download 60 images, I would not advise using a serial queue for the download (e.g. do not use an operation queue with maxConcurrentOperationCount of 1), but rather use a concurrent queue. You will want to synchronize the updates to make sure your code is thread-safe (and this is easily done by dispatching the updates to a serial queue, such as the main queue, for that final update of the model), but I wouldn't suggest using a serial queue for the download itself, as that will be much slower.
If you want to use the NSURLConnection convenience methods, I'd suggest something like the following concurrent operation request approach (where, because it's on a background queue, I'm using sendSynchronousRequest rather than sendAsynchronousRequest), where I'll assume you have an NSArray, imageURLs, of NSURL objects for the URLs of your images:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 4;
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"all done %.1f", CFAbsoluteTimeGetCurrent() - start);
NSLog(#"allImages=%#", self.allImages);
}];
[imageURLs enumerateObjectsUsingBlock:^(NSURL *url, NSUInteger idx, BOOL *stop) {
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url] returningResponse:&response error:&error];
if (!data) {
NSLog(#"%s sendSynchronousRequest error: %#", __FUNCTION__, error);
} else {
UIImage *image = [UIImage imageWithData:data];
if (image) {
dispatch_sync(dispatch_get_main_queue(), ^{
[self.allImages replaceObjectAtIndex:idx withObject:image];
});
}
}
}];
[queue addOperation:operation];
[completionOperation addDependency:operation];
}];
[[NSOperationQueue mainQueue] addOperation:completionOperation];
A couple of asides: First, I'm using an operation queue rather than a GCD concurrent queue, because it's important to be able to constrain the degree on concurrency. Second, I've added a completion operation, because I assume it would be useful to know when all the downloads are done, but if you don't need that, the code is obviously simper. Third, that benchmarking code using CFAbsoluteTime is unnecessary, but useful solely for diagnostic purposes if you want to compare the performance using a maxConcurrentOperationCount of 4 versus 1.
Better than using the NSURLConnection convenience methods, above, you might want to use a NSOperation-based network request. You can write your own, or better, use a proven solution, like AFNetworking. That might look like:
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"all done %.1f", CFAbsoluteTimeGetCurrent() - start);
NSLog(#"allImages=%#", self.allImages);
}];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFImageResponseSerializer serializer];
manager.operationQueue.maxConcurrentOperationCount = 4;
[imageURLs enumerateObjectsUsingBlock:^(NSURL *url, NSUInteger idx, BOOL *stop) {
NSOperation *operation = [manager GET:[url absoluteString] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self.allImages replaceObjectAtIndex:idx withObject:responseObject];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%s image request error: %#", __FUNCTION__, error);
}];
[completionOperation addDependency:operation];
}];
[[NSOperationQueue mainQueue] addOperation:completionOperation];
Because AFNetworking dispatches those completion blocks back to the main queue, that solves the synchronization issues, while still enjoying the concurrent network requests.
But the main take-home message here is that an NSOperation-based network request (or at least one that uses NSURLConnectionDataDelegatemethods) opens additional opportunities (e.g. you can cancel all of those network requests if you have to, you can get progress updates, etc.).
Frankly, having walked through two solutions that illustrate how to download the images efficiently up-front, I feel compelled to point out that this is an inherently inefficient process. I might suggest a "lazy" image loading process, that requests the images asynchronously as they're needed, in a just-in-time (a.k.a. "lazy") manner. The easiest solution for this is to use a UIImageView category, such as provided by AFNetworking or SDWebImage. (I'd use AFNetworking's if you're using AFNetworking already for other purposes, but I think that SDWebImage's UIImageView category is a little stronger.) These not only seamlessly load the images asynchronously, but offer a host of other advantages such as cacheing, more efficient memory usage, etc. And, it's as simple as:
[imageView setImageWithURL:url placeholder:[UIImage imageNamed:#"placeholder"]];
Just a few thoughts on efficiently performing network requests. I hope that helps.

Related

How to run a NSURLConnection in background in iOS

I am working with an app which is todo list organizer, where user adds notes. I am using coredata DB to store the notes. As I am providing sync feature, I am parsing JSON data to server, and also getting JSON data from server.
I am using NSURLConnection API and its delegate functions
- (void)pushData
{
loop through the notes array and send notes 1 by one
[[request setValue:#"application/json;charset=utf-8" forHTTPHeaderField:#"Content-Type"];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:jsonData];
m_dataPush = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
[m_dataPush start];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
Process response from server, save to core DB
and again pushData if any modified and again process the response
}
I call this API, on appEnterBackground and appBecomeActive, because, I want the data to updated on multiple devices.
The problems, which I am facing is that
1) When the notes are more, app is getting stuck, when we exit and open the app and start adding notes.
2) I tried using GCD, but then my NSURLConnection doesnot send me any response
Regards
Ranjit
Ranjit: Based on your comments in the different responses, I suspect you are sending the 1st request from the main thread. When you receive the 1st response, you process it in the background, and then send the 2nd request also from the background. The subsequent requests should be sent from the main thread
[self performSelectorOnMainThread:#selector(myMethodToOpenConnection:)
withObject:myObject
waitUntilDone:NO];
otherwise the thread exits before the delegate is called
You can use NSOperation Queue with NSURLConnection like this
//allocate a new operation queue
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//Loads the data for a URL request and executes a handler block on an
//operation queue when the request completes or fails.
[NSURLConnection
sendAsynchronousRequest:urlRequest
queue:queue
completionHandler:^(NSURLResponse *response,
NSData *data,
NSError *error) {
if ([data length] >0 && error == nil){
//process the JSON response
//use the main queue so that we can interact with the screen
NSString *myData = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
NSLog(#"JSON data = %#", myData);
NSDictionary *myDict = [myData JSONValue];
}
}];
it will do all the processing in the background.
NSURLConnection provides a convenience method called sendAsynchronousRequest: completionHandler: that does the GCD work for you. You can tell it to run the completion handler on the main thread.
Using it, your code would get simpler as follows:
// place a declaration in your .h to make it public
- (void)pushDataWithCompletion:(void (^)(BOOL, NSError*))completion;
- (void)pushDataWithCompletion:(void (^)(BOOL, NSError*))completion
{
// setup your connection request...
[[request setValue:#"application/json;charset=utf-8" forHTTPHeaderField:#"Content-Type"];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:jsonData];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// whatever you do on the connectionDidFinishLoading
// delegate can be moved here
if (!error) {
// did finish logic here, then tell the caller you are done with success
completion(YES, nil);
} else {
// otherwise, you are done with an error
completion(NO, error);
}
}];
}
Exactly what you pass back in the block depends on what the callers care about. It's common to make some aspect of the data you collected one of the block params.
EDIT - I left out the pointer notation (*) after NSError above.
Also, say you have an array of objects that needs to be processed by the server. This method is good for one call. To handle several, lets give it a parameter. Say that each note is an NSString *;
- (void)pushNote:(NSString *)note withCompletion:(void (^)(BOOL, NSError*))completion {
// Code is the same except it forms the request body using the note parameter.
}
If the real task is to do work for several notes, you need a method that calls this one repeatedly, then tells its caller that its done.
- (void)pushNotes:(NSArray *)notes withCompletion:(void (^)(BOOL, NSError*))completion {
// if there are no more notes, we are done
if (!notes.count) return completion(YES, nil);
NSString *nextNote = notes[0];
NSArray *remainingNotes = [notes subarrayWithRange:NSMakeRange(1, notes.count-1)];
[self pushNote:nextNote withCompletion:^(BOOL success, NSError*error) {
// if success, do the rest, or else stop and tell the caller
if (success) {
[self pushNotes:remainingNotes withCompletion:completion];
} else {
completion(NO, error);
}
}];
}

iOS: Why do I need to use sleep(1) after NSURLConnection sendAsynchronousRequest?

I want to display an image on the screen which I take from the internet. I have used
NSURLConnection to create an asynchronous call to take the data and, in the response block, I called the code to assign it to an UIImage object.
My question is why do I need to call sleep(1) after the block execution? If i'm not calling it, then my image is not drawn on the screen. Is it another, more elegant way to achive this?
-(void)loadImage:(NSString *)url
{
NSURL *imageURL = [NSURL URLWithString:url];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSURLRequest *imageRequest = [NSURLRequest requestWithURL:imageURL cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:5.0f];
[NSURLConnection sendAsynchronousRequest:imageRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if(!connectionError) {
if(data) {
//there goes the main thingy
self.myView.wallpaperImage = [UIImage imageWithData:data];
[self.myView setNeedsDisplay];
} else {
NSLog(#"No data found at url:%#",url);
}
} else {
NSLog(#"Could not connect to %#",url);
}
}];
sleep(1);
}
This:
self.myView.wallpaperImage = [UIImage imageWithData:data];
[self.myView setNeedsDisplay];
Is happening on the thread managed by the NSOperationQueue passed to sendAsynchronousRequest. Those methods need to be called from the main thread.
Your sleep may be causing the main thread's runloop to iterate, after which those calls appear to have worked.
To fix this, and to avoid a whole bunch of other problems your current approach will have, do this:
[NSURLConnection sendAsynchronousRequest:imageRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if([data length] > 0) {
//there goes the main thingy
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.myView.wallpaperImage = [UIImage imageWithData:data];
[self.myView setNeedsDisplay];
}];
} else {
// Perform your error handling here.
}
}];
This will use [NSOperationQueue mainQueue] to perform those UIKit calls from the main queue - not libdispatch. libdispatch is a low level interface, it is a recommended best practice to always prefer the higher level interface - in this case, NSOperationQueue. UIKit is only safe when called from the main thread (or queue).
It also changes your error handling behavior to follow the best practices for the platform - check the result of your call (in this case, data) and THEN process any error returned.
Your code is actually a good example of why blocks retain captured objects (in this case self). If there was no retain cycle here, ARC could destroy queue as soon as it goes out of scope, and the block would never execute. Instead, because of the retain cycle, the queue stays around until the block has executed.

How to avoid block retains in Objective-C

I have a queue of blocks which perform a webservice call. The problem is that the downloaded data is not freed after the block's end. I read a lot about retains but I can't make ARC dealloc the memory.
Here's the code:
Create the queue of blocks which download the data
- (void)syncData
{
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_async(queue, ^{
[Model syncAziende:^(id response, NSError *error) {
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
[Model syncContatti:^(id response, NSError *error) {
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
[Model syncDestinazioni:^(id response, NSError *error) {
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
and so on...
});
}
On Model.m
+ (void)syncAziende:(RequestFinishBlock)completation
{
__weak typeof(self)selfObject = self;
[selfObject syncData:^(id response, NSError *error) {
completation(response,error);
} wsEndPoint:kCDCEndPointGetAziende tableName:kCDCDBAziendeTableName];
}
+ (void)syncContatti:(RequestFinishBlock)completation
{
__weak typeof(self)selfObject = self;
[selfObject syncData:^(id response, NSError *error) {
completation(response,error);
} wsEndPoint:kCDCEndPointGetContatti tableName:kCDCDBContattiTableName];
}
// and so on...
Where syncData is:
+ (void)syncData:(RequestFinishBlock)completation wsEndPoint:(NSString*) url tableName:(NSString *)table
{
__weak typeof(self)selfObject = self;
[selfObject getDataFromWS:^(id WSresponse, NSError* WSError)
{
completation(nil,nil);
}WSUrl:url];
}
Where getDataFromWS is:
+ (void)getDataFromWS:(RequestFinishBlock)completation WSUrl:(NSString *)svcUrl
{
__weak typeof(self)selfObject = self;
[selfObject getJsonDataFromURL:^(id response, NSError *error)
{
completation(response,error);
}url:svcUrl];
}
Where getJsonDataFromURL is:
+(void)getJsonDataFromURL:(RequestFinishBlock)completation url:(NSString*)url
{
__weak typeof(self)selfObject = self;
__weak AFHTTPRequestOperationManager *manager = [selfObject getAuthorizedRequestionOperationManager];
[manager.requestSerializer setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[manager.requestSerializer setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[manager.requestSerializer setValue:#"gzip" forHTTPHeaderField:#"Accept-Encoding"];
[manager GET:url parameters:nil success:^(AFHTTPRequestOperation *operation, __weak id responseObject) {
completation([responseObject objectForKey:#"d"],nil);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
completation(nil,error);
}];
}
These __weak references don't really make sense within the context of a class method. It only makes sense to use the weak-self pattern when dealing with instance methods.
So, this begs the question as to which object you're concerned about not getting released. I don't see anything here that would cause the object that has syncData as an instance method to be retained.
Having said that, this routine with the series of network requests that you have made synchronous through the use of the semaphores would (a) retain any autorelease objects that were instantiated throughout the process (notably, the operations themselves); (b) not allow you to cancel the process once it starts; and (c) run the requests serially (for which you'll pay a serious performance penalty and should be avoided unless you absolutely have to).
I'd suggest a simplification of the code. I'd eliminate the dispatch code with the semaphores. I'd also get rid of all of those weak references to self in class methods.
You should simply issue your GET requests. If you really need them to run serially (as implied by your use of the semaphore), then just set the maxConcurrentOperationCount to 1 for the operationQueue of the AFHTTPRequestOperationManager instance. But don't make them run serially unless you absolutely have to, because you pay a significant performance penalty by doing that. But avail yourself of the NSOperationQueue that AFNetworking provides, rather than also doing your own GCD code (and worse, using semaphores within that GCD code).
But this way, if you want to cancel the requests, you can just call cancelAllOperations for the operationQueue of the AFHTTPRequestOperationManager. You can also control the degree of concurrency with maxConcurrentOperationCount.
Given your description and based on the comments, a naive - but still insufficient -
solution may look as follows:
// A generic completion handler:
typedef void (^completion_t)(id result, NSError* error);
- (void) fetchJSONFromURL:(NSURL*)url completion:(completion_t)completion;
- (void) syncAziendeWithInput:(id)input completion:(completion_t)completion;
- (void) syncContattiWithInput:(id)input completion:(completion_t)completion;
- (void) syncDestinazioniWithInput:(id)input completion:(completion_t)completion;
- (void) syncData
{
dispatch_queue_t sync_queue = dispatch_queue_create("sync_queue", 0); // serial queue
NSURL* url = ...;
[self fetchJSONFromURL:url completion:^(id result, NSError*error){
dispatch_async(sync_queue, ^{
[self syncAziendeWithInput:result completion:^(id result, NSError* error){
...
}];
});
}];
url = ...;
[self fetchJSONFromURL:url completion:^(id result, NSError*error){
dispatch_async(sync_queue, ^{
[self syncContattiWithInput:result completion:^(id result, NSError* error){
...
}];
});
}];
url = ...;
[self fetchJSONFromURL:url completion:^(id result, NSError*error){
dispatch_async(sync_queue, ^{
[self syncDestinazioniWithInput:result completion:^(id result, NSError* error){
...
}];
});
}];
...
}
Caveats:
Oversimplification. A practical solution becomes far more elaborated:
No error handling yet.
No way to cancel the asynchronous tasks.
syncData is asynchronous, but has no completion handler. That is, we don't know when it's complete. We could utilize dispatch_group in order to implement an approach to signal the completion of a number of asynchronous methods.
With the help of a third party library we could implement a solution which does all that above, and looks basically even simpler.

iOS: NSOperationQueue: Secondary queue

I would like to receive some data to the server in an asynchronous way and avoiding to overload the App UI performance. Hence would like to send tasks to the secondary queue and not the main one.
This is my current solution which uses the "main queue" ([NSOperationQueue mainQueue] which I understand slows down the performance):
-(NSDictionary*) fetchURL:(NSString*)url
{
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:kCONTACTSINFOURL]];
__block BOOL hasError = FALSE;
__block NSDictionary *json;
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
{
//Verify type of connection error
json = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
NSLog(#"Async JSON: %#", json);
}];
if (hasError) {
[[NSOperationQueue mainQueue] suspend];
return nil;
}
return json;
}
In order to use the secondary queue, and avoid overloading the UI and App performance, is it ok to allocate a shared NSOperationQueue and refer to that? Or is there some other "better" class or method to achieve this?
This would be my improved solution using a secondary NSOperationQueue:
Creating a secondary queue:
NSOperationQueue* otherQueue = [NSOperationQueue init];
Using the other (secondary) queue:
....
[NSURLConnection sendAsynchronousRequest:request
queue:otherQueue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
....
}
Is this correct? Or is there any other way to deal with this?
... following on from the comments...
The NSURLConnection method
[NSURLConnection sendAsynchronousRequest:queue:completionHandler:]
allows you to specify the queue that the completion handler will be called on when the connection is complete. If you suspend this queue you will not stop the requests being sent, you will just stop getting callbacks when they are done. You may want to think about doing this slightly differently...
NSOperation is great, but I tend to prefer going straight for GCD (NSOperation is just a nice obj-c wrapper on the top) if you are hard-set on using NSOperations let me know and i'll add some advice for that.
I assume that you have some kind of manager class that handles all of your server communication? If not, I would recommend that you do, and have it as a singleton.
# interface ChatManager : NSObject
+ (ChatManager *)sharedManager;
#end
#implementation ChatManager {
dispatch_queue_t fetchQueue;
dispatch_queue_t postQueue;
}
+ (ChatManager *)sharedManager {
// This is just the standard apple pattern for creating a singleton
static SCAddressBookManager *sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[SCAddressBookManager alloc] init];
});
return sharedManager;
}
- (id)init {
self = [super init];
if (self) {
// Create a dispatch queue that will run requests one after another, you could make this concurrent but that may cause messages to be lost when you suspend
fetchQueue = dispatch_queue_create("fetch_queue", DISPATCH_QUEUE_SERIAL);
postQueue = dispatch_queue_create("post_queue", DISPATCH_QUEUE_SERIAL);
}
}
- (NSDictionary *)fetchURL:(NSURL *)url {
// In here we will dispatch to our queue and so that the
dispatch_async(fetchQueue, ^{
NSURLRequest = // create your request
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
// Check for error
if (error) dispatch_suspend(fetchQueue); // this suspends the queue that is making the calls to the server, so will stop attempting to send messages when you have an error - you should start pinging to see when you come back online here too, and then use dispatch_resume(fetchQueue) to get it going again!
else {
json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"Async JSON: %#", json);
return json; // ??
}
});
}
- (NSDictionary *)postURL:(NSURL *)url data:(NSData *)bodyData {
// In here we will dispatch to our queue and so that the
dispatch_async(postQueue, ^{
/*
Make your post request.
*/
// Check for error
if (error) dispatch_suspend(postQueue);
else {
json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"Async JSON: %#", json);
return json; // ??
}
});
}
#end
You may also need to use
dispatch_sync(dispatch_get_main_queue(), ^{
// Do stuff on the main thread here
};
if you need to make delegate style callbacks to the main thread!
This is a basic idea on how I would have things setup to try and achieve what you are going for... feel free to let me know if any of this doesn't make sense to you..?
As a side note, I have assumed that for whatever reason you need to have a simple http interface for your server. The much better approach would be to have a persistent socket open between the app and your server, and then you can push data up and down at will. A socket with a heartbeat would also let you know when your connection has gone down. Not sure if maybe you would like me to elaborate on this option some more...

How to batch request with AFNetworking 2?

So I'm rewriting an app for iOS 7 with AFNetworking 2.0 and I'm running into the issue of sending a batch of requests at once and tracking their progress. In the old AFNetworking there was the enqueueBatchOfHTTPRequestOperations:progressBlock:completionBlock: method on AFHTTPClient, this is clearly refactored out and I'm a bit confused on how to enqueue multiple requests.
I have created a subclass of AFHTTPSessionManager and I'm using the POST:... and GET:... methods to communicate with the server. But I can't find anything in the code and/or docs to enqueue multiple requests at once like with the old AFHTTPClient.
The only thing I can find is the undocumented batchOfRequestOperations:progressBlock:completionBlock: method on AFURLConnectionOperation, but that looks like the iOS 6 way of doing this.
Clearly I'm missing something in the new NSURLSession concept that I should use to batch requests or looking over a new AFNetworking feature. Hope someone can help me on the right track here!
tl;dr: How can I send a batch of requests with my AFHTTPSessionManager subclass?
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.
Just updating the thread... I had the same problem and after some researches I found some good solutions, but I decided to stick with this one:
I am using the project called Bolts. So, for the same sample above posted by #Mac_Cain13, it would be:
[[BFTask taskWithResult:nil] continueWithBlock:^id(BFTask *task) {
BFTask *task = [BFTask taskWithResult:nil];
for (int i = 0; i < 10; i++) {
task = [task continueWithBlock:^id(BFTask *task) {
return [self executeEndPointAsync];
}];
}
return task;
}] continueWithBlock:^id(BFTask *task) {
// Everything was executed.
return nil;
}];;
- (BFTask *) executeEndPointAsync {
BFTaskCompletionSource *task = [BFTaskCompletionSource taskCompletionSource];
[self GET:#"endpoint.json" parameters:nil
success:^(NSURLSessionDataTask *task, id responseObject) {
[task setResult:responseObject];
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
[task setError:error];
}];
}];
return task.task;
}
Basically, it's stacking all of the tasks, waiting and unwrapping until there is no more tasks, and after everything is completed the last completion block is executed.
Another project that does the same thing is RXPromise, but for me the code in Bolts was more clear.
For request which can be post or get, you can use AFNetworking 2.0 for batch operation as firstly you need to create operation like this:
//Request 1
NSString *strURL = [NSString stringWithFormat:#"your url here"];
NSLog(#"scheduleurl : %#",strURL);
NSDictionary *dictParameters = your parameters here
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] requestWithMethod:#"POST" URLString:strURL parameters:dictParameters error: nil];
AFHTTPRequestOperation *operationOne = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operationOne = [AFHTTPResponseSerializer serializer];
[operationOne setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
//do something on completion
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"%#",[error description]);
}];
//Request 2
NSString *strURL1 = [NSString stringWithFormat:#"your url here"];
NSLog(#"scheduleurl : %#",strURL);
NSDictionary *dictParameters1 = your parameters here
NSMutableURLRequest *request1 = [[AFHTTPRequestSerializer serializer] requestWithMethod:#"POST" URLString:strURL1 parameters:dictParameters1 error: nil];
AFHTTPRequestOperation *operationTwo = [[AFHTTPRequestOperation alloc] initWithRequest:request1];
operationTwo = [AFHTTPResponseSerializer serializer];
[operationTwo setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
//do something on completion
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"%#",[error description]);
}];
//Request more here if any
Now perform batch operation like this :
//Batch operation
//Add all operation here
NSArray *operations = [AFURLConnectionOperation batchOfRequestOperations:#[operationOne,operationTwo] progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations)
{
NSLog(#"%i of %i complete",numberOfFinishedOperations,totalNumberOfOperations);
//set progress here
yourProgressView.progress = (float)numberOfFinishedOperations/(float)totalNumberOfOperations;
} completionBlock:^(NSArray *operations)
{
NSLog(#"All operations in batch complete");
}];
[[NSOperationQueue mainQueue] addOperations:operations waitUntilFinished:NO];
On AFNetworking 2.0, AFHTTPClient has been split on AFHTTPRequestOperationManager and AFHTTPSessionManager, so probably you could start with the first, which has operationQueue property.
Currently, NSURLSession tasks are not suitable for the same kind of patterns request operations use. See the answer from Mattt Thompson here regarding this issue.
Direct answer: if you need dependencies or batches, you'll still need to use request operations.

Resources