Hi am new to multithreading.... below is the code i wrote to download a video in separate thread but delegates methods not firing up anybody please help me to solve this... thanks in advance..
- (void)downLoad {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
_videoData = [[NSMutableData alloc] init];
[NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://km.support.apple.com/library/APPLE/APPLECARE_ALLGEOS/HT1211/sample_iTunes.mov"]] delegate:self];
//saving is done on main thread
});
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSLog(#"Downloading Started");
_length = [response expectedContentLength];
NSLog(#"Size:%0.2f",_length);
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[_videoData appendData:data];
float progress = (float)[_videoData length]/(float)_length;
NSLog(#"Progress:%0.2f",progress);
_timeLabel.text = [NSString stringWithFormat:#"%0.2F%%",progress*100];
[_progressView setProgress:progress animated:YES];
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(#"didFailWithError");
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
dispatch_async(dispatch_get_main_queue(), ^{
[self saveLocally:_videoData];
NSLog(#"File Saved !");
});
}
NSURLConnection enqueues delegate calls in a run loop (NSRunLoop), which is fine if you create a NSURLConnection from a thread that has one (like the main thread).
But you are using grand central dispatch (GCD) which creates threads and destroys / deactivates them as it likes. I don't really see the need to move download logic to a separate thread, as only the callbacks will be invoked on your main thread (which is pretty useful if you want to do UI work) but NSURlConnection will do its download magic on a background thread.
If you want to do download data processing in the background while downloading, you might want to look into NSURLSession (iOS 7) as it supports GCD queues.
Related
Is it possible to catch NSURLConnection cancel using a callback?
If I'm using this code
-(void) pleaseStopDownload {
cancelled = YES;
[conn cancel];
conn = nil;
[self myUpdateUImessage];
}
from time to myUpdateUImessage is called before this callback
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"didReceiveData");
if (!cancelled) {
//this code inside brackets suddenly is calling:(
//not always but from time to time
summ += data.length;
if (_progressHandler != nil)
_progressHandler(data, summ, max);
} else {
return;
}
}
So the User interface is not updated properly! That is, the final UI is shown than progress UI.
EDIT
the problem was about
NSOperationQueue *tempQueue = [[NSOperationQueue alloc] init];
[conn setDelegateQueue:tempQueue];
correct NSQueue is NSOperationQueue *tempQueue = [NSOperationQueue mainQueue];
Is it possible to catch NSURLConnection cancel using a callback?
No.
From the official documentation here:
After this method is called, the connection makes no further delegate method calls.
This means you should handle the UI cleanup as soon as you call cancel and not rely on the _cancelled variable because - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data is not expected to be invoked anymore.
What I advice, is to call a cleanup method from your cancellation code:
-(void) pleaseStopDownload {
[conn cancel];
conn = nil;
[self handleCancelledDownload];
}
I've read through tons of messages saying the same thing all over again : when you use a NSURLConnection, delegate methods are not called. I understand that Apple's doc are incomplete and reference deprecated methods, which is a shame, but I can't seem to find a solution.
Code for the request is there :
// Create request
NSURL *urlObj = [NSURL URLWithString:url];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:urlObj cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30];
[request setValue:#"gzip" forHTTPHeaderField:#"Accept-Encoding"];
if (![NSURLConnection canHandleRequest:request]) {
NSLog(#"Can't handle request...");
return;
}
// Start connection
dispatch_async(dispatch_get_main_queue(), ^{
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]; // Edited
});
...and code for the delegate methods is here :
- (void) connection:(NSURLConnection *)_connection didReceiveResponse:(NSURLResponse *)response {
NSLog(#"Receiving response: %#, status %d", [(NSHTTPURLResponse*)response allHeaderFields], [(NSHTTPURLResponse*) response statusCode]);
self.data = [NSMutableData data];
}
- (void) connection:(NSURLConnection *)_connection didFailWithError:(NSError *)error {
NSLog(#"Connection failed: %#", error);
[self _finish];
}
- (void) connection:(NSURLConnection *)_connection didReceiveData:(NSData *)_data {
[data appendData:_data];
}
- (void)connectionDidFinishDownloading:(NSURLConnection *)_connection destinationURL:(NSURL *) destinationURL {
NSLog(#"Connection done!");
[self _finish];
}
There's not a lot of error checking here, but I've made sure of a few things :
Whatever happens, didReceiveData is never called, so I don't get any data
...but the data is transfered (I checked using tcpdump)
...and the other methods are called successfully.
If I use the NSURLConnectionDownloadDelegate instead of NSURLConnectionDataDelegate, everything works but I can't get a hold on the downloaded file (this is a known bug)
The request is not deallocated before completion by bad memory management
Nothing changes if I use a standard HTML page somewhere on the internet as my URL
The request is kicked off from the main queue
I don't want to use a third-party library, as, ultimately, these requests are to be included in a library of my own, and I'd like to minimize the dependencies. If I have to, I'll use CFNetwork directly, but it will be a huge pain in the you-know-what.
If you have any idea, it would help greatly. Thanks!
I ran into the same problem. Very annoying, but it seems that if you implement this method:
- (void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *)destinationURL
Then connection:didReceiveData: will never be called. You have to use connectionDidFinishLoading: instead... Yes, the docs say it is deprecated, but I think thats only because this method moved from NSURLConnectionDelegate into NSURLConnectionDataDelegate.
I like to use the sendAsynchronousRequest method.. there's less information during the connection, but the code is a lot cleaner.
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
if (data){
//do something with data
}
else if (error)
NSLog(#"%#",error);
}];
From Apple:
By default, a connection is scheduled on the current thread in the
default mode when it is created. If you create a connection with the
initWithRequest:delegate:startImmediately: method and provide NO for
the startImmediately parameter, you can schedule the connection on a
different run loop or mode before starting it with the start method.
You can schedule a connection on multiple run loops and modes, or on
the same run loop in multiple modes.
Unless there is a reason to explicitly run it in [NSRunLoop currentRunLoop],
you can remove these two lines:
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[connection start];
or change the mode to NSDefaultRunLoopMode
NSURLConnection API says " ..delegate methods are called on the thread that started the asynchronous load operation for the associated NSURLConnection object."
Because dispatch_async will start new thread, and NSURLConnection will not pass to that other threat the call backs, so do not use dispatch_async with NSURLConnection.
You do not have to afraid about frozen user interface, NSURLConnection providing only the controls of asynchronous loads.
If you have more files to download, you can start some of connection in first turn, and later they finished, in the connectionDidFinishLoading: method you can start new connections.
int i=0;
for (RetrieveOneDocument *doc in self.documents) {
if (i<5) {
[[NSURLConnection alloc] initWithRequest:request delegate:self];
i++;
}
}
..
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
ii++;
if(ii == 5) {
[[NSURLConnection alloc] initWithRequest:request delegate:self];
ii=0;
}
}
One possible reason is that the outgoing NSURLRequest has been setup to have a -HTTPMethod of HEAD. Quite hard to do that by accident though!
I am trying to make a Asynchronous Call , a Synchronous one. I know its not a better idea to do it. But, I do need such to code to handle Auth Challenge of Self Signed Certificate while Keeping the call still as Synchronous.
But, I am not sure whether it is a perfect way to make Asycnh call a Synch one.
-(NSData*) startConnection{
NSURLConnection *conn=[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
while(!isFinished && [[NSRunLoop currentLoop] runMode: NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]){
}
return responseAppData;
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
//Code to handle Certificate
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[responseAppData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
isFinished=YES;
}
I also thought of using the while Loop as below, so which one should be used?
while(!isFinished ){
}
Actually it's the opposite. If you want to handle these NSURLConnectionDelegate methods, you need to use asynchronous calls, NOT synchronous. Otherwise the delegates are never called.
typedef void (^onDownload)(NSData *data);
#property (nonatomic,assign) onDownload block;
-(void) startConnectionwithBlock:(onDownload) pBlock;{
self.block = [pBlock copy];
}
-(void) connectionDidFinishLoading:(NSURLConnection *)connection{
block(self.data);
}
I've changed my old NSthread because couldn't update UI from a background thread. Now I'm using dispatch_async.
The problem is when this code is executed the second time, in ApplicationDidBecomeActive (when user hide and show again de application) Never exit from that while, keeps in infinite loop.
Here I show the new code about dispatch_async, the NSURLConnection code always worked , resume at the end... I don't think the problem is there
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// first connection to the server through NSURLConnection, downloading a json list
[wsDataVideos getVideosData:URL_WS_VIDEOS];
dispatch_queue_t secondDownloadQueue = dispatch_queue_create("name",NULL);
dispatch_async(secondDownloadQueue, ^{
// wait for wsDataVideos has finished. IT SEEMS THIS COLLAPSES CPU ¿?
while (wsDataVideos.imported==NO) {}
// If are there new videos begins sincronous and slower download:
if ....{
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI: inform user begins download
[menuViewController.badgeVideos setBadgeVideosText:#"Downloading..."];
});
// Download videos (sincro)
dispatch_async(dispatch_get_main_queue(), ^{
// informs user is completed
[menuViewController.badgeVideos setBadgeVideosText:#"Downloaded"];
});
}
});
}
The first connection (json), than I wait in that while :
-(void)getVideosData:(NSString *)url_ws{
NSLog(#"Get videos data1 ");
if (wsData){
[wsData setLength:0];
}
else{
wsData=[[NSMutableData alloc] init];
}
NSURLRequest *reqCat=[NSURLRequest requestWithURL:[NSURL URLWithString:url_ws] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
NSURLConnection *conCat=[[NSURLConnection alloc] initWithRequest:reqCat delegate:self];
}
with their methods:
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
...
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[wsData appendData:data];
}
...
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
...
Any ideas? thanks again.
If I put code inside while{} it works, but i think this is somthing odd..chunk, bad code, isn't it?
Maybe a better soluttion would be somthing like dispatch_group_wait() ?
// wait for wsDataVideos has finished. IT SEEMS THIS COLLAPSES CPU ¿?
while (wsDataVideos.imported==NO) {
sleep(1)
}
How can I get NSURLConnection to call it's delegate methods from a different thread instead of the main thread. I'm trying to mess around with the scheduleInRunLoop:forMode:but doesn't seem to do what I want.
I have to download a large file and it interrupts the main thread so frequently that some rendering that is happening starts getting choppy.
NSURLRequest * request = [NSURLRequest requestWithURL:url];
NSURLConnection * connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
NSRunLoop * loop = [NSRunLoop currentRunLoop];
NSLog(#"loop mode: %#",[loop currentMode]);
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[connection start];
The other thing I don't see much of is "Modes" There are only two modes documented so not much really to test with.
Any ideas?
Thanks
There are several options:
In your implementation of the delegate methods, make use of dispatch_async.
Start the schedule the connection on a background thread.
You can do the latter like this:
// all the setup comes here
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSRunLoop *loop = [NSRunLoop currentRunLoop];
[connection scheduleInRunLoop:loop forMode:NSRunLoopCommonModes];
[loop run]; // make sure that you have a running run-loop.
});
If you want a guarantee on which thread you're running, replace the call to dispatch_get_global_queue() appropriately.
If you want to perform downloads on a separate thread, I'm pretty sure these are the droids you're looking for...
- (void) dispatchRequest{
self->finished = NO;
NSMutableURLRequest* request = //Formulate your request
NSThread* download_thread = [[NSThread alloc] initWithTarget:self selector:#selector(downloadThreadLoop:) object:request];
[download_thread start];
}
- (void) downloadThreadLoop:(NSMutableURLRequest*) request{
NSURLConnection* connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
while(!self->finished]){
//This line below is the magic!
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
//...
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
//...
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
//...
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
//...
self->finished = YES;
}
If you truly need to do the download in a new thread, it may be easier to detachNewThreadSelector:toTarget:withObject:, setup (and destroy) an NSAutoreleasePool, and then use one of the synchronous selectors like NSData's dataWithContentsOfURL:. This will not make use of the asynchronous NSURLConnectionDelegate.
Because this call is synchronous, it will not return until the file has been downloaded, which will block the main thread, but because you're in a new thread, it won't. Please note that this is typically discouraged behavior. Is there other code happening in the main thread that can be optimized?
NSURLConnection is already doing the download off of the main thread asynchronously. If I understand your question, you would like the delegate messages to be sent on a thread other than the main thread? You can't do that as you can't modify the internal implementation of NSURLConnection. I can think of two ways to simulate this.
Create a sublcass of NSURLConnection (e.g. MyURLConnection) that assigns itself as own delegate. Note that this creates an intentional retain cycle so be careful. MyURLConnection should define a new delegate that supports NSURLConnectionDelegate. Let's call this finalDelegate. When MyURLConnection handles it's own delegate messages, forward or dispatch them to finalDelegate on whatever thread you like.
Similar to option #1 but without the subclass. Handle the NSURLConnection delegate methods on the main thread and forward/dispatch them to whatever thread you like.
The main difference is if you want a reusable subclass that behaves this way or it's a one off implementation.
EDIT: added suggestion on how to run code in the background
If you are going to process the response in the background I would either use operations or grand central dispatch. No need to mess around with run loops and creating threads. Check out Apple's Concurrency Programming Guide.