Does -dataWithContentsOfURL: of NSData work in a background thread?
No, it doesn't.
In order to get data from URL asynchronously you should use the NSURLRequest and NSURLConnection approach.
You will have to implement the NSURLConnectionDelegate methods:
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
-(void)connectionDidFinishLoading:(NSURLConnection *)connection;
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
I'm using dataWithContentsOfURL in a background thread fine.
-(void)loaddata {
NSData* data = [NSData dataWithContentsOfURL:#"some url"];
if (data == nil) {
DLog(#"Could not load data from url: %#", url);
return;
}
}
Call something like this from main thread.
[self performSelectorInBackground:#selector(loaddata) withObject:nil];
If you want to perform updates to ui at end of loaddata, be sure to call a function on main thread.
No. You can use NSURLSession instead, though.
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
NSString *imageURL = #"Direct link to your download";
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
NSURLSessionDownloadTask *getImageTask = [session downloadTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:imageURL]] completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *downloadedImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:location]];
});
}];
[getImageTask resume];
No, it blocks the current thread.
You need to use NSURLConnection in order to have asynchronous requests.
Also you may use -dataWithContentsOfURL + NSOperation + NSOperationQueue
I'm guessing this has changed a bit over the years. But, these days,
NSURLRequest* request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:urlString]];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse* response, NSData* data, NSError* error) {
}];
will give you an async network call.
No, this will block the thread and you will load the contents of file into the RAM. You can download content directly into file without temporary NSData to avoid huge RAM usage. Something like this solution https://stackoverflow.com/a/6215458/2937913
Related
I am currently using this code to get data from a URL:
NSURL *url = [[NSURL alloc] initWithString:urlstring];
NSString *stringfromFB = [[NSString alloc] initWithContentsOfURL:url];
I was wondering how I could gather this data asynchronously so that my app does not freeze everytime I need to execute this. Thanks!
The simplest way is available in os5 like this:
NSString *stringfromFB;
NSURL *url = [[NSURL alloc] initWithString:urlstring];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (data) {
stringfromFB = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding]; // note the retain count here.
} else {
// handle error
}
}];
If you're stuck in os<5 for some reason, you'll need to start your connection with a delegate, and implement the delegate protocol as illustrated here (and many places elsewhere).
You can do it with GCD:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSURL *url = [[NSURL alloc] initWithString:urlstring];
NSString *stringfromFB = [[NSString alloc] initWithContentsOfURL:url]
});
// Do not alloc init URL obj. for local use.
NSString *urlString = #"put your url string here";
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
- (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
The above delegate methods are of NSURLConnectionDelegate, where you need to handle all things like response error etc.. It is by default provided so we can directly override it without
I have use once in my project It will work for async request but If you received number of images as well then also use IconDownloader or EGOImageView which implements the lazy loading of image and make much low chances of freezing of app.
if you don’t want to wait for a connection to complete loading a url, you can load it asynchronously using NSURLConnection.
[NSURLConnection connectionWithRequest:
[NSURLRequest requestWithURL:
[NSURL URLWithString:yourUrlString]]
delegate:self];
Now that NSURLConnection is deprecated you need to use NSURLSession.
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest: request
completionHandler: ^(NSData *data, NSURLResponse *response, NSError *networkError) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if ([httpResponse statusCode] == STATUS_OK) {
// use data
}
else {
NSLog(#"Network Session Error: %#", [networkError localizedFailureReason]);
}
}];
[task resume];
I'm trying to get some data from an URL, but for some reason, nothing happens when I do the following. Neither didReceiveResponse:, didReceiveData:, didFailWithError: or connectionDidFinishLoading: are reached, except when I add a timeout to my request by doing this: [request setTimeoutInterval:10.0]
Here's what I'm doing :
-(void)getConfigFromServer{
[self getContentAtURL:kUrlGetUser];
}
//Va chercher le contenu à l'URL passée en paramètre
- (void)getContentAtURL: (NSURL *)url {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSString * userLogin = [userDefaults objectForKey:#"UserLogin"];
NSString * userPassword = [userDefaults objectForKey:#"UserPassword"];
NSURL * urlFinal = [NSURL URLWithString:[NSString stringWithFormat:#"%#", url]];
NSLog(#"Request : %#", [NSString stringWithFormat:#"%#", url]);
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:urlFinal];
[request setHTTPShouldHandleCookies:NO];
[request setTimeoutInterval:10.0];
NSString *sourceString = [NSString stringWithFormat:#"%#:%#", userLogin, userPassword];
NSData * sourceData = [sourceString dataUsingEncoding:NSUTF8StringEncoding];
NSString *authString = [sourceData base64EncodedString];
authString = [NSString stringWithFormat: #"Basic %#", authString];
[request setValue:authString forHTTPHeaderField:#"Authorization"];
[request setValue:#"text/xml" forHTTPHeaderField:#"Accept"];
NSURLConnection * connection=[NSURLConnection connectionWithRequest:request delegate:self];
if(connection){
NSLog(#"Connection started");
receivedData = [NSMutableData data];
}else{
NSLog(#"Error while trying to initiate the connection");
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[receivedData setLength:0];
if ([response respondsToSelector:#selector(statusCode)])
{
int statusCode = [((NSHTTPURLResponse *)response) statusCode];
if (statusCode >= 400)
{
[connection cancel]; // stop connecting; no more delegate messages
NSDictionary *errorInfo
= [NSDictionary dictionaryWithObject:[NSString stringWithFormat:
NSLocalizedString(#"Server returned status code %d",#""),
statusCode]
forKey:NSLocalizedDescriptionKey];
NSError *statusError = [NSError errorWithDomain:#"Error"
code:statusCode
userInfo:errorInfo];
[self connection:connection didFailWithError:statusError];
}
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[receivedData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
NSLog(#"Connection failed! Error - %# %#",[error localizedDescription],[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[self fetchedData:receivedData];
}
EDIT : I'm still having this problem, and now have it on the actual device too. As I said it in the comments, I use ARC on this app for the first time, and I'm using XCode 4.2.
Any idea?
This is old thread but still this answer might help someone out there.
If you are calling this in background thread, check whether you thread is exiting before delegates is called.
Try doing this.
NSURLConnection * connection = [[NSURLConnection alloc]
initWithRequest:request
delegate:self startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop mainRunLoop]
forMode:NSDefaultRunLoopMode];
[connection start];
Error message, as you have told in comments, is saying that the request has timed out.
I am sure that if you remove the line with timeout, you will get either the same response or actual data, after 60 seconds. Make sure you wait enough, because if your connection is very weak for some reason, the request may not time out after 60 seconds because it keeps downloading data.
Is your app in foreground while you launch this? Make sure you don't suspend it.
Furthermore, you say that the server is online, but your code is timing out. Means something is probably wrong with your connection after all.
In another comment you say that sometimes it works. Even more points to the fact that the connection is weak/unreliable or breaking up.
You're passing an NSURL to your getContentAtURL but then treating it as though it was a string in :
NSURL * urlFinal = [NSURL URLWithString:[NSString stringWithFormat:#"%#", url]];
try changing it to:
NSURL * urlFinal = [NSURL URLWithString:[NSString stringWithFormat:#"%#", [url absoluteString]]];
btw, you know you're not using your login and password don't you?
EDIT:
When you say receivedData = [NSMutableData data]; what is data?
I think that might be the problem.
I tend to use the following to setup the data object when data first arrives:
- (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)incrementalData {
if (receivedData ==nil) { receivedData = [[NSMutableData alloc] initWithCapacity:2048]; }
[receivedData appendData:incrementalData];
}
Some thoughts here:
Please do not store passwords in UserDefaults, use the keychain for that (maybe you are only testing, I just wanted to have this mentioned)
Why do you convert your NSURL url to a new NSURL by calling stringWithFormat: on the url? (Oh, I see #ade already mentioned that)
Is this an ARC-enabled App? If not, your NSMutableData will be autoreleased too soon, use self.receivedData = [NSMutableData data] and you will see a crash.
Try making your NSURLConnection an instance variable and hold on to it. I think it should work the way you do it right now, but I tend to hold on to it and never had the problem you mention here.
Weird that your 10 seconds timeout seems to work, I had trouble getting this to work and found that on iPhone, the timeout can not be lower than 240 seconds. Yes, I was confused about this as well, but: https://stackoverflow.com/a/3611383/148335
I think the problem will be with server side. Try to increase TimeoutInterval to 60 so there will be more time to fetch data from server. Didn't you get "connection timed out" message after 10 seconds?
EDIT
Hi, I think still you are not reached a solution. When I read the code in detail, I found some statements are confusing.
1) [request setValue:#"text/xml" forHTTPHeaderField:#"Accept"]; replace with
[request setValue:#"text/plain" forHTTPHeaderField:#"Accept"];
Also include,
[request setValue:#"text/plain" forHTTPHeaderField:#"Content-Type"];
And finally...
The error "connection timed out" indicates that the iPhone does not receiving any data from server. As soon as iPhone gets a bye of data it will call - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data. Try to put NSLog after each statements to verify variables hold desired value. Try to check the length of data --sourceData-- before sending ...
I am calling NSURLConnection asynchronous method calls in my view controller. I would like to handle TWO RESPONSES FOR TWO REQUEST in the same Delegate. Please suggest me what would the best approach to achieve this? I'm developing in iOS 5 SDK.
UPDATED:
// Class A
[serverconns setDelegate:self];
connection = [serverconns executeAsyncHttpPost :firstjsonrequest];
[serverconns setDelegate:self];
connection = [serverconns executeAsyncHttpPost :secondjsonrequest];
- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
}
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.appendData appendData:data];
}
- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
// logs the error
}
- (void) connectionDidFinishLoading:(NSURLConnection *)connection
{
NSData *responseData = [[NSData alloc] initWithData:appendData];
//HOW CAN WE HANDLE TWO RESPONSES FOR TWO REQUEST in the same Delegate
if (responseData)
{
// doing something
}
}
//Class B: ServerConnection
- (NSURLConnection *) executeAsyncHttpPost :(id) jsonParams
{
NSString *urlstr = [NSString stringWithFormat:#"%#", baseURL];
urlstr = [urlstr stringByAppendingFormat:method];
NSURL *pUrl = [NSURL URLWithString:urlstr];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:pUrl];
NSData *requestData = [NSData dataWithBytes:[jsonParams UTF8String] length:[jsonParams length]];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-type"];
[request setHTTPBody: requestData];
return [[NSURLConnection alloc] initWithRequest:request delegate:delegateResponder startImmediately:YES];
}
-(void) setDelegate:(id)newDelegate
{
delegateResponder = newDelegate;
}
save your connections somewhere (maybe ivar of your delegate)
- (void) connectionDidFinishLoading:(NSURLConnection *)connection
{
NSData *responseData = [[NSData alloc] initWithData:appendData];
//HOW CAN WE HANDLE TWO RESPONSES FOR TWO REQUEST in the same Delegate
if (responseData)
{
if (connection == yourFirstConnection) {
// doing something for first connection
} else {
// doing something for second connection
}
}
}
just point out some minor problem of your code
NSString *urlstr = [NSString stringWithFormat:#"%#", baseURL];
urlstr = [urlstr stringByAppendingFormat:method];
should replace to
NSString *urlstr = [baseURL absoluteString];
urlstr = [urlstr stringByAppendingString:method];
and add two(or more or array) weak/assign property of NSURLConnection to your class A (connection delegate)
#property (assign) NSURLConnection *myFirstConnection;
#property (assign) NSURLConnection *mySecondConnection;
// assume only need to handle two connection otherwise NSArray should be used instead
than in your class B (create connection)
- (NSURLConnection *) executeAsyncHttpPost :(id) jsonParams
{
NSString *urlstr = [baseURL absoluteString];
urlstr = [urlstr stringByAppendingString:method];
NSURL *pUrl = [NSURL URLWithString:urlstr];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:pUrl];
NSData *requestData = [NSData dataWithBytes:[jsonParams UTF8String] length:[jsonParams length]];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-type"];
[request setHTTPBody: requestData];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:delegateResponder startImmediately:YES];
delegateResponder.myFirstConnection = connection;
// delegateResponder.mSecondConnection = connection;
return connection;
}
If I were you I would create a CustomClass which inherits the NSURLConnection. And I will add property called tag.
When I initiate the CustomClass, I would set the tag property and use that to determine which request is being worked on
CustomURLConnection *connection = [[CustomURLConnection alloc] initWithRequest:request delegate:self tag:1];
- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate tag:(int)_tag
{
if(self = [super initWithRequest:request delegate:delegate])
{
self.tag = _tag;
}
Now in the code you posted add this
- (void) connectionDidFinishLoading:(NSURLConnection *)connection
{
NSData *responseData = [[NSData alloc] initWithData:appendData];
//HOW CAN WE HANDLE TWO RESPONSES FOR TWO REQUEST in the same Delegate
if (responseData)
{
if (connection.tag == 1){
}
}
}
return self;
}
I think all the mentioned solutions are "ugly". I would not implement a solution with delegate methods but instead create a blocks-based solution. I could post an example if you're interested. I would make use of the AFNetworking classes for this approach.
What follows is an example of a class that handles 2 different responses without using a delegate implementation, opting for blocks instead with the AFNetworking library.
- (void)JSONFromService
{
// create the first request and set the methods that handle the return values (either NSData or NSError in this case) in blocks ...
NSURL *firstURL = [NSURL URLWithString:#"http://dl.dropbox.com/u/6487838/test1.html"];
NSURLRequest *firstRequest = [NSURLRequest requestWithURL:firstURL];
AFHTTPRequestOperation *firstOperation = [[AFHTTPRequestOperation alloc] initWithRequest:firstRequest];
[firstOperation setCompletionBlockWithSuccess:^ (AFHTTPRequestOperation *operation, id object)
{
NSString *firstString = [[NSString alloc] initWithData:object encoding:NSASCIIStringEncoding];
NSLog(#"%#", firstString);
} failure:^ (AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#", error);
}];
[firstOperation start];
// create the second request and set the methods that handle the return values (either NSData or NSError in this case) in blocks ...
NSURL *secondURL = [NSURL URLWithString:#"http://dl.dropbox.com/u/6487838/test2.html"];
NSURLRequest *secondRequest = [NSURLRequest requestWithURL:secondURL];
AFHTTPRequestOperation *secondOperation = [[AFHTTPRequestOperation alloc] initWithRequest:secondRequest];
[secondOperation setCompletionBlockWithSuccess:^ (AFHTTPRequestOperation *operation, id object) {
NSString *secondString = [[NSString alloc] initWithData:object encoding:NSASCIIStringEncoding];
NSLog(#"%#", secondString);
} failure:^ (AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#", error);
}];
[secondOperation start];
}
I usually subclass NSURLConnection and add properties to store whatever context I need to handle the response.
Since the delegate methods get NSURLConnection passed in, you can just cast it back to your subclass and access the context.
Take a look at this example.
I think you should keep all of your connections in an activeConnections array. Every time one finishes, you do [activeConnections indexForObject:connection] and you update your delegate method accordingly, using the index.
Now, a cleaner way to do it( and a better way from my point of view, but this depends on how large is the data you want to transfer) is to use queues. I'll provide a small example and add comments to it:
// we assume you have 2 requests: req1, req2
//now, create a new dispatch queue
dispatch_queue_t netQueue = dispatch_queue_create("com.mycompany.netqueue",DISPATCH_QUEUE_SERIAL);
//execute the operations in the queue ASYNC
//is very important to dispatch this ASYNC on a background thread, otherwise your UI will be stuck until the request finishes
dispatch_async(netQueue,
^{
// We are on a background thread, so we won't block UI events (or, generally, the main run loop)
NSHTTPURLResponse *response = nil;
NSError *error = nil;
NSData *data = nil;
//We can call the request synchronous and block this thread until completed
data = [NSURLConnection sendSynchronousRequest:req1
returningResponse:&response
error:&error];
dispatch_async(dispatch_get_main_queue(),
^{
//call your delegate with the appropriate method for req1
//be sure to copy the contents in data, as we will reuse it with the next request
});
//We can call the other request synchronous and block this thread until completed
data = [NSURLConnection sendSynchronousRequest:req2
returningResponse:&response
error:&error];
dispatch_async(dispatch_get_main_queue(),
^{
//call your delegate with the appropriate method for req2
});
//and this can go on forever. If you have many requests to execute, simply put them in a loop
});
dispatch_release(netQueue);
I would like to know how do I get a return value 1 or 0 only.... back from an URL request asynchronously.
currently I do it in this way:
NSString *UTCString = [NSString stringWithFormat:#"http://web.blah.net/question/CheckQuestions?utc=%0.f",[lastUTCDate timeIntervalSince1970]];
NSLog(#"UTC String %#",UTCString);
NSURL *updateDataURL = [NSURL URLWithString:UTCString];
NSString *checkValue = [NSString stringWithContentsOfURL:updateDataURL encoding:NSASCIIStringEncoding error:Nil];
NSLog(#"check Value %#",checkValue);
this works, however it is blocking my main thread till I got a reply back from the URL, how do I set it so it will do it in a another thread instead of the main thread ?
EDIT: ANSWER
I end upcalling my function with this, it works well :)
[self performSelectorInBackground:#selector(shouldCheckForUpdate) withObject:nil];
you can use NSURLConnection class
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
[[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
and handle its response and errors using its delegate methods.
- (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
You can find implementation of NSURLConnection
Apple docs: Using NSURLConnection
How To Use iOS NSURLConnection By Example
Edit: Although NSURLConnection is provided by apple is more recommended way of placing URL request. But I found AFNetworking library very time saving, easy to implement and robust yet simple as third party implementation. You should give it a try.
try this :
.h:
NSMutableData *responseData;
.m:
- (void)load
{
NSURL *myURL = [NSURL URLWithString:#"http://www.example.com"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:myURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
responseData = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[responseData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[responseData release];
[connection release];
[textView setString:#"Unable to fetch data"];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"Succeeded! Received %d bytes of data",[responseData
length]);
NSString *txt = [[[NSString alloc] initWithData:responseData encoding: NSASCIIStringEncoding] autorelease];
}
Use NSURLConnection and make your request.
Then you may start synchronous or asynchronous connection with NSURLConnection's methods :
Loading Data Synchronously
+ sendSynchronousRequest:returningResponse:error:
Loading Data Asynchronously
+ connectionWithRequest:delegate:
– initWithRequest:delegate:
– initWithRequest:delegate:startImmediately:
– start
Check the NSURLConnection class in Apple Developer API Reference.
Shamelessly copy from https://gist.github.com/knmshk/3027474. All credits go to https://gist.github.com/knmshk.
xmlData = [[NSMutableData alloc] init];
NSURL *url = [NSURL URLWithString:
#"http://forrums.bignerdranch.com/smartfeed.php?"
#"limit=NO_LIMIT&count_limit20&sort_by=standard&"
#"feed_type=RSS2.0&feed_style=COMPACT"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[NSURLConnection sendAsynchronousRequest:request
queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
if (error) {
xmlData = nil;
NSLog(#"error:%#", error.localizedDescription);
}
[xmlData appendData:data];
}];
There is an example in the iOS XCode documentation called LazyTableImages. This does an asynchronous URL as well as asynchronous image load into UITableView cells displayed on the screen after scrolling stops. Excellent example of protocols, asynchronous data handling, etc.
my app downloads a video from internet and saves it in the iPhone.
I'm using this code
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease];
[request setHTTPMethod:#"GET"];
NSError *error;
NSURLResponse *response;
NSString *documentFolderPath = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents"];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *videosFolderPath = [documentFolderPath stringByAppendingPathComponent:#"videos"];
//Check if the videos folder already exists, if not, create it!!!
BOOL isDir;
if (([fileManager fileExistsAtPath:videosFolderPath isDirectory:&isDir] && isDir) == FALSE) {
[[NSFileManager defaultManager] createDirectoryAtPath:videosFolderPath withIntermediateDirectories:YES attributes:nil error:nil];
}
NSString *filePath = [videosFolderPath stringByAppendingPathComponent:#"name.mp4"];
if ([fileManager fileExistsAtPath:filePath ] == YES) {
NSLog (#"File exists");
}
else {
NSLog (#"File not found");
NSData *urlData;
NSString *downloadPath = #"http://www.mywebsite.com/name.mp4";
[request setURL:[NSURL URLWithString:downloadPath]];
urlData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
BOOL written = [urlData writeToFile:filePath atomically:NO];
if (written)
NSLog(#"Saved to file: %#", filePath);
else {NSLog(#"problem");}
}
All works fine, but I want to add a progress View, to indicate the status of the download.
The problem (I think but I'm not sure) is that my NSURLConnection hasn't itself as delegate, so methods like
- (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)data
or
-(void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite{
are never called.
I tried to use
urlData = [[NSURLConnection alloc] initWithRequest:request delegate:self];
but the app crashes when I try to write the file
[urlData writeToFile:filePath atomically:NO];
What can I do?
Thanks
What you are doing is sending a "synchronous" request, meaning that the thread that is downloading the HTTP response will hang until all data has been fetched. It is never good to do this on a UI thread, even when you do not want display any indicator of the download's progress. I suggest using the NSURLConnection class, setting it's delegate, and responding to delegate methods. A small tutorial for this can be found at http://snippets.aktagon.com/snippets/350-How-to-make-asynchronous-HTTP-requests-with-NSURLConnection.
Once you are a connection's delegate, you can get the content length when the connection receives a response:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSHTTPURLResponse * httpResponse = (NSHTTPURLResponse *)response;
contentSize = [httpResponse expectedContentLength];
}
Then, to calculate the total progress of the download whenever new data arrives, do something like this:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// download data is our global NSMutableData object that contains the
// fetched HTTP data.
[downloadData appendData:data];
float progress = (float)[downloadData length] / (float)contentSize;
[progressView setValue:progress];
}
UPDATE:
Another example on how to do async HTTP with Foundation: http://codewithchris.com/tutorial-how-to-use-ios-nsurlconnection-by-example/
You are using synchronous request here.
The document says -
A synchronous load is built on top of the asynchronous loading code made available by the class. The calling thread is blocked while the asynchronous loading system performs the URL load on a thread spawned specifically for this load request. No special threading or run loop configuration is necessary in the calling thread in order to perform a synchronous load.
Refer the class reference here
After you have read the class reference, you can either send asynchronous request or initialize the request, set delegate, and start.