Simultaneous NSURLDownloads - objective-c

I have a Cocoa Mac application set up to download files to a specific folder using NSURLDownload. This works great with a single download at a time. However, if I attempt to start multiple downloads, all but the last will fail immediately.
Is there any way to use NSURLDownload for multiple simultaneous downloads? Or what would be a good way to queue up multiple URLs to be downloaded in order? Or is there a more appropriate way to accomplish this (NSURLConnection seemed possible but I was unsure if I could set the download location and filename as I can with NSURLDownload)?

Each NSURLDownload represents a single downloading instance. You're probably trying to reuse the same one multiple times. It's an inherently asynchronous system that already used background threads. Here's an example based on Apple's sample code:
- (void)startDownloadingURL:sender
{
// Create a couple requests.
NSURLRequest *requestOne = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.apple.com"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
NSURLRequest *requestTwo = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://stackoverflow.com"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// Create two download instances
NSURLDownload *downloadOne = [[NSURLDownload alloc] initWithRequest:requestOne delegate:self];
NSURLDownload *downloadTwo = [[NSURLDownload alloc] initWithRequest:requestTwo delegate:self];
if (downloadOne) {
// Set the destination file.
[downloadOne setDestination:#"/tmp" allowOverwrite:YES];
} else {
// inform the user that the download failed.
}
if (downloadTwo) {
// Set the destination file.
[downloadTwo setDestination:#"/tmp" allowOverwrite:YES];
} else {
// inform the user that the download failed.
}
}
- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
{
// Release the connection.
[download release];
// Inform the user.
NSLog(#"Download failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSErrorFailingURLStringErrorKey]);
}
- (void)downloadDidFinish:(NSURLDownload *)download
{
NSLog(#"The download %# has finished.", download)
// Release the download connection.
[download release];
}
If you attempt to use the same NSURLDownload for both NSURLRequests, then it will kill the previous connection.

I'd second using NSOperation if your on 10.5+ or greater. You could just throw 1 operation onto a queue for each download. Or you could even just use sendSynchronous request and use it with NSOperationQUeue's addOperationWithBlock (10.6+) method and then in your block you are throwing onto the queue you can just use [[NSOperationQueue mainQueue] addOperationWithBlock:^{ when you are doe with the code you need to execute or just periodically need to refresh the UI on the main thread, like so...
[myQueue addOperationWithBlock:^{
//download stuff here...
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
//update main thread UI
}];
}];
you would just need to do this for each download.

If you're targeting 10.5+, you should look at NSOperation. It should allow you to build a generic solution for a single download, and then use the built-in queue facilities to manage dependencies if you require certain operations finish downloading before others begin.
Keep in mind that these APIs often expect delegate methods involved to be run on the main thread, so you'll need ensure that occurs if you're working with asynchronous APIs that function via delegate methods. (You can do this pretty simply by using performSelectorOnMainThread: and friends)

Related

Background uploading concerns (objective c)

We are working on one iOS application that needs to upload photos, videos to amazon s3. We need to do below tasks for this application:
Upload video, image in the background
If network connection is lost, alert the user
If user has low signal, give a message to the user for trying this upload later (we are storing all upload files in local till it we complete upload)
We are using NSURLRequest for uploading all files in the background. But we are not sure if this is the correct approach because it has lot of issues. Can someone recommend best approach and methods to use in iOS for above tasks.
thats fine if you want to support ios6 as well -- for ios7+ switch to NSURLSession
beware though that iOS does not guarantee background time and may cancel your process at any time
to check for that, register a backgroundHandler which offers an expiry block that is called before the os does kill your process
tell os you want to run a bg task:
UIBackgroundTaskIdentifier token = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"Ranging for region %# killed", region.identifier);
}];
if(token == UIBackgroundTaskInvalid) {
NSLog(#"cant start background task");
}
do whatever you want to do ..
when done, tell os:
[[UIApplication sharedApplication] endBackgroundTask:token];
Note: this rarely works indefinitely and it meant to be used that way. The OS can still decide to kill you! (chances for your app to run a longer time are better if you don't use much memory and as little cpu / bandwidth as possible)
regarding your use case:
if your NSURLConnection fails your completionBlock or the delegate gets an error.
you can show a UILocalNotification then
dummy code using blocks:
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data,
NSError *connectionError) {
if(connectionError) {
UILocalNotification *theNotification = [[UILocalNotification alloc] init];
theNotification.alertBody = connectionError.description;
theNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:1];
[[UIApplication sharedApplication] scheduleLocalNotification:theNotification];
}
}];

Objective-c simple polling

I'm new to objective-c and I'm building an app that requires a background polling to a generic API to refresh some data on my user interface.
After several hours looking for an answer/example that fits my problem I came across some solutions like the following:
long polling in objective-C
polling an external server from an app when it is launched
Poll to TCP server every hour ios
http://blog.sortedbits.com/async-downloading-of-data/
but unfortunately none of them covers my scenario which is really basic:
I need to start a polling when viewDidLoad, let's say an infinite loop, and on every iteration, let's say every 10 seconds, to call an API and when I didReceiveData I want to log that data into the console with an NSLog, obviously this can't be done on the main thread.
What I really need is a very simple example on how to do that, and with simple I mean:
I can't implement long polling/push notifications for many reasons and not looking for a way to do it, lets' just assume I cannot.
I don't want to rely on fancy frameworks like LRResty, RESTKit, AFNetworking or anything else since I don't really need them and also I can't believe that there is no SDK bulletin that can cover this basic scenario.
I don't need anything else besides what I described, so no authentication, no parameters in my request, no response handling etc. (since I'm new to objective-c and stuff not strictly needed could just be more confusing to me...)
The solution I'm looking for could be something like this (using NSOperationQueue to run my loop into a separate thread):
- (void)viewDidLoad {
[super viewDidLoad];
//To run polling on a separate thread
operationQueue = [NSOperationQueue new];
NSInvocationOperation *operation=[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(doPolling) object:nil];
[operationQueue addOperation:operation];
}
-(void)doPolling {
MyDao *myDao = [MyDao new];
while (true) {
[myDao callApi];
[NSThread sleepForTimeInterval:10];
}
}
// and in MyDao
-(void)callApi {
NSMutableString *url= [NSMutableString stringWithFormat:#"%#",#"http:www.example.it"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
request.HTTPMethod = #"GET";
self.conn= [[NSURLConnection alloc] initWithRequest:request delegate:self];
if(self.conn){
[self.conn start];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"didReceiveData %s","yes");
}
but unfortunately as stated here: Asynchronous NSURLConnection with NSOperation looks like I cannot do that.
Please help, I refuse to believe that there isn't a simple straightforward solution for this basic scenario.

App not waiting for method to finish executing

I have a method getnamefornumbers which call a soap based web service(sudzc generated), which return a some data which i store in array to use.
But problem is that when i call the method it takes its time to execute and code after this method also executing, this result in null array.
what can i do so when this method completes its work then rest of the code executes.
You have to use custom delegates.You should define the protocol and delegate the current class to responsible for the class which performs getnamefornumbers. Once the operation done , you should return to caller class.
Here is the example of protocols http://mobiledevelopertips.com/objective-c/the-basics-of-protocols-and-delegates.html
You should use the NSURLConnection delegation methods. In an async environment that's the normal behavior:
You make a call (in an async way)
The application keeps running (after you make the 1. call the program continues with the rest of the instructions)
So you have to two solutions, make it sync, so you will only continue after an answer comes (in your case the array is filled), which I would probably disencourage. Or, you make it async, and use the array when you actually have it.
As for specifics in how to implement this, more details must be provided, in order for me to advise you.
Update 1.0
-(void)requestConnectionToServer{
NSURL *url= [NSURL URLWithString:#"myWebServiceURL"];
NSMutableURLRequest *theRequest=[NSMutableURLRequest requestWithURL:url];
self.reference=aReference;
[theRequest setHTTPMethod:#"GET"];
[theRequest setTimeoutInterval:20.0];
[NSURLConnection connectionWithRequest:theRequest delegate:self];
}
#pragma mark NSURLConnectionDelegate Implementation
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSLog(#"Response:%#",[[NSString alloc] initWithData:webData encoding:NSASCIIStringEncoding]);
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(#"ERROR with theConenction %#",error);
}
Update 2.0
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
myArray = [MyWebServiceAccess getnamefornumbers];
dispatch_sync(dispatch_get_main_queue(), ^{
[myArray makeSomething];
});
});

Download a cocoa bundle from server

how can I download a Cocoa .bundle file from a server and then load it into a app? I've tried using a zip but the shouldDecodeSourceDataOfMIMEType function doesn't get called.
- (IBAction)testDownload:(id)sender {
// Create the request.
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://localhost/SampleBundle.zip"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// Create the connection with the request and start loading the data.
NSURLDownload *theDownload = [[NSURLDownload alloc] initWithRequest:theRequest
delegate:self];
if (theDownload) {
// Set the destination file.
[theDownload setDestination:#"/Users/developer/Desktop/Test-Downloads/SampleBundle" allowOverwrite:YES];
} else {
// inform the user that the download failed.
}
}
- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
{
download = nil;
// Inform the user.
NSLog(#"Download failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)downloadDidFinish:(NSURLDownload *)download
{
download = nil;
// Do something with the data.
NSLog(#"%#",#"downloadDidFinish");
}
- (BOOL)download:(NSURLDownload *)download shouldDecodeSourceDataOfMIMEType:(NSString *)encodingType;
{
BOOL shouldDecode = YES;
NSLog(#"EncodingType: %#",encodingType);
return shouldDecode;
}
So how can I download a .bundle from a server uncompress it and load it into the application?
Thanks
According to the documentation of download:shouldDecodeSourceDataOfMIMEType:
This method is not called if the downloaded file is not encoded.
So, I'm guessing that might have something to do with it. You're probably better off implementing download:didReceiveResponse: and examining the NSURLResponse object, especially the status code -- if that's not 200 than something is going wrong and you need to look at HTTP codes to see what exactly the problem is.
Also, I'm not sure of this, do you need elevated permissions to install a bundle, it being an executable?
The shouldDecodeSourceDataOfMIMEType delegate works great but only on gzip (.gz) archives. I've tested extensively with both .zip & .gz.
It should also be noted that it doesn't call tar, so if you applied compression and tar at the same time as in:
tar czvf ArchiveName.tar.gz ./ArchiveName/
the shouldDecodeSourceDataOfMIMEType delegate will leave you with:
ArchiveName.tar
So, the archive will not be immediately useable.
For .zip archives, as others have pointed out, your best bet is MiniZip (C API) or a Objective-C framework based on it like ZipArchive (2005) or the more recent SSZipArchive (2013).

Prevent `performSelectorInBackground:` from running twice?

So I'm using performSelectorInBackground:#selector(loadStuff) to load things in the background. It takes some time.
The user may want to re-load the items, while the method above is running in the background. If I performSelectorInBackground:#selector(loadStuff) again while the other method is already running, I get all kinds of errors.
Is there an easy way to handle this situation?
I'd like to stop the already-running-in-the-background method, then start the new one. (Or if there is a better way of accomplishing the end goal, that would be fine as well).
If you want to start over, you can cancel the connection and create a new one. Since you're going to run this method on a background thread you need to make sure that only one thread can access the related instance variables at a time:
- (void)loadStuff
{
#synchronized(self) {
if (currentConnection != nil)
[currentConnection cancel];
[currentConnection release];
}
currentConnection = [[NSURLConnection alloc] initWithRequest:request
delegate:self
startImmediately:YES];
}
}
Another way to do it is to use a flag to indicate a busy state. For example, if you wanted to just return if the long-running method were already in process on another thread, you could do this:
- (void)loadStuff
{
#synchronized(self) {
if (loadingStuff == YES)
return;
}
loadingStuff = YES;
}
NSURLRequest *request = ...
NSURLResponse *response;
NSError *error;
[NSURLConnection sendSynchronousRequest:request returningReseponse:&response error:&error];
#synchronized(self) {
loadingStuff = NO;
}
}