11

I'm facing problem with downloading 5 MB file, it taking more then 2 minutes on iPhone 5 with iOS 6.1.

Using iPhone 4S with same iOS version it taking only 10 seconds, both are using WiFi.

I have tried different cache Policy and timeout Interval of NSURLRequest, it changed nothing, it's still taking long time. Download is over HTTP.

I'm using NSURLConnection class, before downloading this "big" file I'm downloading two others.

Don't know what else can be important,to reduce the time.

Thanks in advance.

Code:

@interface MyClass : NSObject <NSURLConnectionDelegate>
{
  @private id delegate;
  NSURLConnection *connection;
  NSMutableData* responseData;
  //...
}


#import "MyClass.h"

@implementation MyClass

-(void)getObj1:(id)delegateObj
{
   delegate = delegateObj;

   NSString *url = @"...";

   NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:120.0];

   connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];

   if(connection)
   {
      responseData = [NSMutableData data];
   }
}

-(void)getObj2:(*String)somesString
{

   NSString *url = @"...";

   NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:120.0];

   connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];

   if(connection)
   {
    responseData = [NSMutableData data];
   }
}

-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{ 
    //....
}


-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
   [responseData appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
   if(firstRequest)
   {
      //save data from first request and set responseData clear
      [self getObj2:@"..."];
   }
}

and others without anything special, I hope this will be enough

I have found this post https://devforums.apple.com/message/754875#754875 but still doesn't work fine for me. However now I better understand this strange situation.

Meryl
  • 193
  • 3
  • 10
  • Your code is important. If you could post how you create and start the request, and the delegate methods. – Mike D Mar 23 '13 at 18:52
  • Any idea? Maybe this link from my edit will help, I'm out of ideas – Meryl Mar 24 '13 at 19:31
  • You should rather create _two_ objects of your `MyClass` in order to perform _two_ different downloads. You can quickly confuse the ivars. Also, ensure that you invoke [[NSURLConnection alloc] init ...] on that thread where you want the delegate to be executed. Possibly the main thread - otherwise you need to know how to setup a secondary thread for that purpose which is a bit tricky. – CouchDeveloper Jun 05 '13 at 19:04
  • Try to release and nil your first request's NSURLConnection and create NEW connection to do second request. Maybe the first connection slow down your request, I guess. – fannheyward Jun 06 '13 at 03:35

8 Answers8

1

Use AFDownloadRequestOperation (AFNetworking "sublass") - you can have also pause/resume operation.

Here you have an example https://stackoverflow.com/a/12209618

Community
  • 1
  • 1
TonyMkenu
  • 7,597
  • 3
  • 27
  • 49
0

Have you tried AFNetworking? It' s a wrapper on NSURLConnection. I'm not sure if it would help you in getting a faster download, but it sure does give you an edge over NSURLConnection.

Neeku
  • 3,646
  • 8
  • 33
  • 43
kiran
  • 21
  • 3
  • Based on this topic http://stackoverflow.com/questions/12570483/ios6-large-downloads-time-out?rq=1 I assume that it won't help but I'm going to check that in next step. Thanks for answer. – Meryl Mar 23 '13 at 18:53
0

You used GCD dispatch_async queue to execute set of NSURLRequest request to download data from server or getting server response.

NSString *webURL = @"http://therealurl.appspot.com/?format=json&url=bit.ly/a";
NSURL *url = [NSURL URLWithString:webURL];
NSURLRequest *awesomeRequest = [NSURLRequest requestWithURL:url];
NSURLConnection *connection=[[NSURLConnection alloc] initWithRequest:awesomeRequest delegate:self];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
    NSRunLoop *loop=[NSRunLoop currentRunLoop];
    [connection scheduleInRunLoop:loop forMode:NSRunLoopCommonModes];
    //[self processTheAwesomeness];
});
karan singh rajpoot
  • 5,401
  • 1
  • 15
  • 12
  • 1
    A connection should not be scheduled via `scheduleInRunLoop:forMode:` in a dispatch queue trying to get its run loop. That won't work since the run loop can disappear at any time (if any avail at all) and and the underlaying thread can change from delegate to delegate any time, too. – CouchDeveloper Jun 05 '13 at 19:00
  • What he said. If you want to handle your own scheduling like this and keep it off the main thread, you need to spawn (and manage) the background thread yourself. That said, in the case of a single transfer, with no other significant activity on the main thread, there would be no reason not to simply schedule it on the main thread. – ipmcc Jun 06 '13 at 12:09
0

I'd propose a solution where you have some kind of a communication manager which has an NSOperationQueue it handles, with a single entry point method where you give it a the URL where the content lives you want to download and a success and a failure block. The communication manager than creates an NSOperation where you create the NSURLRequest and handle the callbacks.

As soon as the commninication manager puts the operation onto the queue its start method is called. In my communication manager implementation I keep track (besides putting the operations onto the queue) of every started operation via an NSMutableDictionary so that you can cancel a single or all operations (In the sample code provided the operationKey is used for this purpose. Here the JSONOperation returns (in case of success) an NSString to the communicator but it could be any kind of data too, e.g. I use the same class for downloading images, so I'd pass back the data object itself. Below you can find my JSONOperation class as an example. I you like the idea I could put the other files on Gist.

My NSOperation looks like this

@interface JSONOperation : NSOperation <NSURLConnectionDataDelegate, OperationDelegate>  
  + (instancetype)jsonOperationForProvider:(id)provider success:(OperationSuccessWithString)success failure:(OperationFailure)failure;  
@end  

#import "JSONOperation.h"
#import "ProviderDelegate.h"

@interface JSONOperation()
@property (nonatomic, assign) BOOL executing;
@property (nonatomic, assign) BOOL finished;
@property (nonatomic, assign) BOOL cancelled;

@property (nonatomic, strong) NSURL *url;

@property (nonatomic, weak) id <ProviderDelegate> delegate;
@property (nonatomic, strong) NSURLConnection *connection;
@property (nonatomic, strong) NSMutableData *receivedData;

@property (nonatomic, copy) OperationFailure failure;
@property (nonatomic, copy) OperationSuccessWithString success;
@end

@implementation JSONOperation 

- (void)start
{
    if ([self isCancelled])
{
    [self willChangeValueForKey:@"isFinished"];
    _finished = YES;
    [self didChangeValueForKey:@"isFinished"];

    return;
}

    NSURLRequest *request = [NSURLRequest requestWithURL:self.url];
    self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
    [self.connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
    [self.connection start];

    [self willChangeValueForKey:@"isExecuting"];
    _executing = YES;
    [self didChangeValueForKey:@"isExecuting"];
}

- (NSMutableData *)receivedData
{
    if (nil == _receivedData) {
        _receivedData = [NSMutableData data];
    }
    return _receivedData;
}

+ (instancetype)jsonOperationForProvider:(id <ProviderDelegate>)provider success:(OperationSuccessWithString)success failure:(OperationFailure)failure
{
    NSAssert(nil != provider, @"provider parameter can't be nil");

    JSONOperation *operation = [[[self class] alloc] init];
    operation.delegate = provider;
    operation.url = provider.contentURL;
    operation.failure = failure;
    operation.success = success;

    return operation;
}

- (BOOL)isConcurrent {
    return YES;
}

- (BOOL)isExecuting {
    return _executing;
}

- (BOOL)isFinished {
    return _finished;
}

- (BOOL)isCancelled {
    return _cancelled;
}

#pragma mark - NSURLConnectionDataDelegate

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {

    if (_success) {
        NSString *receivedText = [[NSString alloc] initWithData:self.receivedData encoding:NSUTF8StringEncoding];
        _receivedData = nil;

        self.success(self, receivedText);
    }

    [self willChangeValueForKey:@"isFinished"];
    [self willChangeValueForKey:@"isExecuting"];

    _executing = NO;
    _finished = YES;

    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [self.receivedData appendData:data];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    [connection cancel];
    _connection = nil;
    _receivedData = nil;
    _url = nil;

    if (_failure) {
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            self.failure(self, error);
    }];
    }

    [self willChangeValueForKey:@"isFinished"];
    [self willChangeValueForKey:@"isExecuting"];

    _executing = NO;
    _finished = YES;

    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
}

#pragma mark - OperationDelegate

- (NSString *)operationKey
{
    return [self.url absoluteString];
}

- (id)provider
{
    return _delegate;
}

- (void)cancelOperation
{
    _failure = nil;
    _success = nil;

    [self.connection cancel];
    _connection = nil;

    _receivedData = nil;
    _url = nil;

    [self willChangeValueForKey:@"isCancelled"];
    [self willChangeValueForKey:@"isFinished"];
    [self willChangeValueForKey:@"isExecuting"];

    _executing = NO;
    _finished  = YES;
    _cancelled = YES;

    [self didChangeValueForKey:@"isCancelled"];
    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
}

@end

EDIT - Gist Sample files

Bernd Rabe
  • 790
  • 6
  • 23
0

Well in my experience AFNetworking does an awsome job in handling the downloads.I am using a downloading operation on files with 10+ MB size .So I strongly suggest using it.

My answer on stack to show progressbar.See the answer where i implement both the ways ,with AFNetworking and NSUrlconnection.You can try both ways and can see the progress and you can calculate how the bytes get downloaded in each packet.By tracking it so you can analyse how it varies in the download time. Try it

Community
  • 1
  • 1
Lithu T.V
  • 19,955
  • 12
  • 56
  • 101
0

Just try using gzip to compress the remote file for NSURLRequest. It'll speed up your connection dramatically.

To use this, you need to have it installed on the server, and the good news is if you're using apache2 on your server, it comes by default. To test to make sure your server/URL had gzip compression enabled, test it with this online tool: http://www.feedthebot.com/tools/gzip/

If the answer is yes, proceed to add the code to your Objective-C code in Xcode. After this line in your code:

NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:120.0];

just add this:

// Create a mutable copy of the immutable request and add more headers
NSMutableURLRequest *mutableRequest = [request mutableCopy];

//add gzip compression
[mutableRequest addValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"];

// Now set our request variable with an (immutable) copy of the altered request
request = [mutableRequest copy];

This will speed up your response time noticeably, and you do not need to use AFNetworking for a small NSURLRequest or NSURLConnection task.

Neeku
  • 3,646
  • 8
  • 33
  • 43
-1

I'm not sure what your problem is, but the following code works reliably for me.

- (id)init
{
  self.downloadQueue = [[NSOperationQueue alloc] init];
  [self.downloadQueue setMaxConcurrentOperationCount:1];
}

- (void)doDownload:(NSURL *)url
{
  [self.downloadQueue addOperation:[NSBlockOperation blockOperationWithBlock:^{

    NSData *data =[NSData dataWithContentsOfURL:url];

    dispatch_sync(dispatch_get_main_queue(), ^{
      NSAutoreleasePool *mainQueuePool = [[NSAutoreleasePool alloc] init];

      ... update user interface ...

      [mainQueuePool release];
    });

  }]];
}
Abhi Beckert
  • 32,787
  • 12
  • 83
  • 110
-1

I think it's a problem in your device. Try another device from a friend.

Undo
  • 25,519
  • 37
  • 106
  • 129
Bimawa
  • 3,535
  • 2
  • 25
  • 45