Mackey18
Mackey18

Reputation: 2352

Asynchronous network requests semi-crashing app

So whenever my ViewController is loaded I do an upload to a server. There seems to be a problem however. When there are a substantial number of asynchronous requests it can semi-crash the app. What I mean by that is that the requests just won't go through, and no other requests (which are on another thread) proceed. On top of that, the keyboard goes supremely laggy (weird I know). Anyway, so It's a serious issue considering the other network requests don't get sent because of it. What I find weird is that the upload requests are the same in number as the download requests (and the uploads don't even do anything, they just make a normal http request), yet the download requests work fine in any quantity. Here is my code:

- (void)serverUploadAll:(myEntity *)send
{  
    NSMutableString *urlString = [[NSMutableString alloc] initWithString:ServerApiURL];
    NSString *addTargetUrl = [NSString stringWithFormat:@"/addtarget?deviceToken=%@&appVersion=%@&targetId=%@", postToServer.deviceToken, postToServer.appVersion, send.Id];
    [urlString appendString:addTargetUrl];

    NSURL *url = [NSURL URLWithString:urlString];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    [NSURLConnection sendAsynchronousRequest:request queue:queueTwo completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
        //NSLog(@"response=%@", response);
        //NSLog(@"data=%@", data);
        //NSLog(@"error=%@", error);
    }];

}

The code above is called as a database is cycled through, with the problem arising when there are a substantial amount of calls, i.e. 120+. My download requests are actually done using AFNetworking, so perhaps that is why they work efficiently. Anyway, to summarise, why when the above code is called multiple times does it just jam, and stop?

Thanks for the help, really appreciated.
Regards,
Mike

UPDATE: So, thanks to the brilliant answer by runmad, I'm using the AFNetworking approach, however, it crashes with this error -[AFHTTPRequestOperation _propertyForKey:]: unrecognized selector sent to instance. I don't get why that isn't working, here is my code:

- (void)cycleThroughEntries
{
MyEntity *myEntity;
NSMutableArray *urlRequests;
urlRequests = [[NSMutableArray alloc] init];

for (id i in fetchedResultsController.fetchedObjects) {
    myEntity = i;
    NSMutableString *urlString = [[NSMutableString alloc] initWithString:ServerApiURL];
    NSString *addTargetUrl = [NSString stringWithFormat:@"/addtarget?deviceToken=%@&appVersion=%@&targetId=%@", postToServer.deviceToken, postToServer.appVersion, myEntity.Id];
    [urlString appendString:addTargetUrl];
    //NSLog(@"URL sent is: %@", urlString);
    NSURL *url = [NSURL URLWithString:urlString];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    AFHTTPRequestOperation *requestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
    [urlRequests addObject:requestOperation];
}

AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:@""]];

[client enqueueBatchOfHTTPRequestOperationsWithRequests:urlRequests
                                        progressBlock:^(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations) {

                                            NSLog(@"%d / %d", numberOfCompletedOperations, totalNumberOfOperations);

                                        }
                                        completionBlock:^(NSArray *operations) {
                                            NSLog(@"All Done!");
                                        }];

}

Not entirely sure what's wrong, so would appreciate the help. Thanks.

UPDATE 2: Fixed. I shouldn't have been making an array of AFHTTPRequestOperations, but of just normal NSURLRequests. Problem solved!!

Upvotes: 2

Views: 659

Answers (1)

runmad
runmad

Reputation: 14886

There is a limit to how many asynchronous connections you can make - even if they occur on background threads. 120+ network calls at the same time sounds a bit crazy, especially if they include uploads which take longer than a GET request.

I would suggest you consider rewriting what you need to request/upload to the server to reduce the amount of requests. You need to ensure that the calls don't all happen at the same time and you hold off on most calls until others are completed.

Enter NSOperationQueue...

I would create a manager class which handles all your requests. In this class you create an NSOperationQueue where you can keep adding requests to it. Here's a good tutorial. You can set the number of concurrent requests and the NSOperationQueue will ensure the next requests in the queue wait until the currently running requests are done. It does a lot of heavy lifting for you with networking.

You may also consider having a look at AFNetworking's NSOperationQueues to queue up all your network calls so they don't all occur at the same time.

In AFNetworking's AFHTTPClient You may be able to use the following method depending on how you're setting up your requests:

- (void)enqueueBatchOfHTTPRequestOperationsWithRequests:(NSArray *)urlRequests
                                          progressBlock:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progressBlock
                                        completionBlock:(void (^)(NSArray *operations))completionBlock;

That should get you started :) Good luck.

UPDATE

You can also add operations to the AFNetworking operationsQueue if you want. You just have to make sure to start it if it's not currently running already. This way you're able to add additional requests from other parts of the app at various times. You can always check if any operations are running/in queue and the awesome thing is that is allows you to easily cancel any running operations. It can be found here.

Upvotes: 4

Related Questions