I am wondering whether the completionHandler block is called on the main thread, when I use NSURLConnection sendAsynchronousRequest. My main concern is whether I need to dispatch UIKit calls on the main thread by myself.
The document for NSURLConnection did not mention this detail, unless I missed it.
I profiled my code, and there was no memory leak, which kind of indicating that the block was executed on the main thread.
Is there any document that gives definite answer?
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
[self.progress stopAnimating];
if (data != nil) {
...
} else {
[self showMessageIgnoreAcknowledgement:#"Error" message:#"blah"];
}
}];
From the documentation for the NSURLConnection sendAsynchronousRequest:queue:completionHandler: method:
Loads the data for a URL request and executes a handler block on an operation queue when the request completes or fails.
Also from the comment about the queue parameter:
The operation queue to which the handler block is dispatched when the request completes or failed.
Since you are passing in the main queue, the completion handler will be on the main thread (the main queue is on the main thread).
Related
I have a problem. I call some method in dispatch_async. But in callMethod2 in different object I am uploading image with [NSURLConnection sendAsynchronousRequest. But after upload, It doesn't show me response. (However when I call callMethod2 without dispatch_async, it works great). Where can be the problem?
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
[offline callMethod1];
[offline callMethod2];
});
Upload image
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSLog("Never show me me this log");
}];
You're calling NSLog on a thread that isn't the main thread (aka calling it asynchronously) so you won't see NSLog as it must run on the main thread.
You can have the completion block notify you some other way when it's done. The best way I've found is using https://github.com/kseebaldt/deferred which allows you to send a promise saying (I promise I'll do this thing and notify you when it's done).
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.
I have an iOS application which is using an NSOperationQueue, NSOperations and AFNetworking 2.1.0 to fire off requests to a server. The -[NSOperation main] method looks something like:
- (void)main {
AFHTTPSessionManager *sessionManager = [AFHTTPSessionManager sharedSessionManager];
[sessionManager GET:#"url"
parameters:nil
success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(#"Success");
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"Failure");
}
];
}
I have noticed that, from time to time, that the callbacks for a particular operation never get executed, when multiple operations are created and added to the NSOperationQueue in quick succession. I dove into AFNetworking to try to figure out why. I ended up in -[AFURLSessionManager dataTaskWithRequest:completionHandler], which looks like:
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];
AFURLSessionManagerTaskDelegate *delegate = [AFURLSessionManagerTaskDelegate delegateForManager:self completionHandler:completionHandler];
[self setDelegate:delegate forTask:dataTask];
return dataTask;
}
I added a logging statement right after dataTask is created:
NSLog(#"Task with id %# created for %# on queue %#", #(dataTask.taskIdentifier), request.URL.path, dispatch_get_current_queue());
The log reveals the problem:
2014-02-26 14:11:25.071 App[50094:6a2f] Task with id 15 created for /url1 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:25.071 App[50094:460f] Task with id 16 created for /url2 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:26.274 App[50094:6a2f] Task with id 18 created for /url2 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:26.274 App[50094:6c17] Task with id 17 created for /url1 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:27.546 App[50094:6307] Task with id 20 created for /url2 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:27.546 App[50094:6b17] Task with id 19 created for /url1 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:28.705 App[50094:6b17] Task with id 21 created for /url1 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:28.705 App[50094:6307] Task with id 21 created for /url2 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:32.091 App[50094:6307] Task with id 22 created for /url2 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:32.091 App[50094:6b17] Task with id 23 created for /url1 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
Notice the fourth set in the log has the same taskIdentifier which is what AFNetworking uses to associate tasks with their callbacks, via delegate.
If I force the NSOperations to run on the main queue, then I am unable to recreate the issue - the taskIdentifier is always unique.
Has anyone seen anything like this before? Do I need to ensure that -[NSURLSession dataTaskWithRequest:] runs only on the main thread in order to not get taskIdentifier collisions?
Don't know if this is still relevant to you or not, but this exact thing had me banging my head around all night. Turns out you are partially answering the problem in your question.
As it would turn out, if
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];
is run asynchronously, then there is the small chance that the instance of NSURLSession will assign the same taskIdentifier to different tasks.
Without forking AFNetworking and synchronizing on all of the dataTaskWithRequest: methods then there were two ways that I could go about fixing this that stood out.
If you don't need the NSURLSessionTask returning from this method then the best way would be to make your own dispatch queue and any requests you want to make with your session manager you just send it to that queue asynchronously like so:
static dispatch_queue_t my_queue;
my_queue = dispatch_queue_create("MyQueueName", DISPATCH_QUEUE_CONCURRENT);
// ... Later, that very same day
dispatch_async(my_queue, ^{
// [sessionManager GET: ...
});
The only problem with this method for your issue however was that it all seemed to be executed on the same operation queue, so maybe this way wouldn't work in your case. The other way (which is how I did it) is actually waay simpler. Just synchronize on the AFURLSessionManager so the invocation of dataTaskWithRequest: can only ever happen synchronously like so:
#synchronized(sessionManager) {
// [sessionManager GET: ...
}
Or, yes, you could just do the task creations on the main thread. But for some projects its not always as simple as that.
I would enqueue a NSURLSessionDataTask to your AFURLSessionManager instead of trying to directly make GET requests. The session manager is intended to abstract the functionality of NSURLSession.
Example:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:#"http://example.com/testapi.php"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(#"error!");
} else {
NSLog(#"task successful!");
}
}];
[dataTask resume];
I want to execute a series of AFJSONRequestOperation in order, and be able to interrupt the queue when one fails.
At the moment, the way I do it is not reliable, as sometimes the next operation will get a chance to start.
I have a singleton to call my api endpoint
AFJSONRequestOperation *lastOperation; // Used to add dependency
NSMutableArray *operations = [NSMutableArray array]; // Operations stack
AFAPIClient *httpClient = [AFAPIClient sharedClient];
[[httpClient operationQueue] setMaxConcurrentOperationCount:1]; // One by one
And then I add the operations this way
NSMutableURLRequest *request = ...; // define request
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
// Takes care of success
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
[[httpClient operationQueue] setSuspended:YES];
[[httpClient operationQueue] cancelAllOperations];
}];
[push:operation addDependency:lastOperation];
[operations $push:operation]; // This is using ConciseKit
lastOperation = operation;
// Repeat with other operations
// Enqueue a batch of operations
[httpClient enqueueBatchOfHTTPRequestOperations:operations ...
Trouble is, sometimes the operation following the one that fails still gets a chance to start.
So it seems that that having 1 concurrent operation max and a dependency chain isn't enough to tell the queue to wait until after the failure callback is fully executed.
What's the proper way to do this ?
Thanks
The failure callback is executed on the main thread and the operation (which is running on a background thread) doesn't wait for it. So, you'd need to do some editing to prevent the next operation from starting before the operation and its completion blocks have completed.
Or, instead of putting all the operations into the queue at the start, hold the list of operations in an array and just add the next operation following each success.
I currently have the following code in an NSOperation that has an observer for keyPath "isCancelled":
downloaded = FALSE;
NSURL *url = [NSURL URLWithString:requestString];
dataXML = [[NSData alloc] initWithContentsOfURL:url];
downloaded = TRUE;
I want to make it so that the observeValueForKeyPath function is able to cancel the dataXML continuing or just completely stop the NSOperation once the NSOperation is sent a cancel message. The NSOperation's cancelling operation cancel only notifies the operation that it should stop, but will not force my operation's code to stop.
You can't cancel it.
If you want to be able to cancel the load mid-way through, use NSURLConnection operating in asynchronous mode. It's a bit more work to set up but you can cancel at any point in the download process.
Alternatively, you could use this handy class I wrote that wraps an async NSURLConnection and its delegate in a single method call ;-)
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[[RequestQueue mainQueue] addRequest:request completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (data && error == nil)
{
//do something with your downloaded data
}
}];
//to cancel the download at any time, just say
[[RequestQueue mainQueue] cancelRequest:request];
Easy!
</shamelessSelfPromotion>
Note that the request above is already asynchronous, and the class already manages queuing of multiple requests, so you don't need to (and shouldn't) wrap it in an NSOperationQueue.