Почему NSURLConnection истекает при отправке большого количества запросов? - PullRequest
2 голосов
/ 28 января 2012

Я пишу сетевой класс для приложения для iOS. Этот класс позаботится обо всем журнале и сетевом трафике. У меня есть проблема, когда я должен отправлять, возможно, тысячи запросов одновременно, но NSURLConnections истекают по тайм-ауту, потому что методы делегата не будут вызываться до тех пор, пока не будут запущены все NSURLConnections, к этому времени истекает период ожидания. Я использую API отдыха для Drupal и, к сожалению, я не знаю, как создать несколько экземпляров одним запросом. Как я могу получить ответы, одновременно отправляя их? Если я использую GCD для создания NSURLConnections, это решит проблему? Я думаю, что мне пришлось бы пройти всю операцию итерации по объектам для отправки и отправки в GCD, чтобы освободить основной поток для ответа на ответы.

-(BOOL)sendOperation:(NetworkOperation)op 
     NetworkDataType:(NetworkDataType)dataType
          JsonToSend:(NSArray *)json
          BackupData:(NSArray *)data
{
    if(loggingMode)
    {
        return YES;
    }
    NSURLConnection *networkConnection;
    NSData *send;
    NSString *uuid = [self generateUUID];
    NSMutableArray *connections = [[NSMutableArray alloc] init];
    NSMutableURLRequest *networkRequest;
    for (int i=0; i<[json count] && (data ? i<[data count] : YES); i++) 
    {
        if(op == Login)
        {
            /*Grab all cookies from the server domain and delete them, this prevents login failure
             because user was already logged in. Probably find a better solution like recovering
             from the error*/
            NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:
                                [[NSURL alloc] initWithString:networkServerAddress]];
            for (NSHTTPCookie *cookie in cookies) 
            {
                [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];
            }
            networkRequest = [[NSMutableURLRequest alloc] initWithURL:
                          [NSURL URLWithString:[networkServerAddress stringByAppendingString:@"/user/login"]]];
        }
        else if(op == StartExperiment)
        {
            networkRequest = [[NSMutableURLRequest alloc] initWithURL:
                              [NSURL URLWithString:[networkServerAddress stringByAppendingString:@"/node"]]];
        }
        else if(op == Event || op == EndExperiment || op == SendAll)
        {
            networkRequest = [[NSMutableURLRequest alloc] initWithURL:
                              [NSURL URLWithString:[networkServerAddress stringByAppendingString:@"/node"]]];
        }
        else if(op == Logout)
        {
            networkRequest = [[NSMutableURLRequest alloc] initWithURL:
                              [NSURL URLWithString:[networkServerAddress stringByAppendingString:@"/user/logout"]]];
        }

        send = [[json objectAtIndex:i] dataUsingEncoding:NSUTF8StringEncoding];
        //Set the headers appropriately
        [networkRequest setHTTPMethod:@"POST"];  
        [networkRequest setValue:@"application/json"    
              forHTTPHeaderField: @"Content-type"];
        [networkRequest setValue:[NSString stringWithFormat:@"%d", [send length]]  
              forHTTPHeaderField:@"Content-length"]; 
        [networkRequest setValue:@"application/json"    
              forHTTPHeaderField:@"Accept"];
        //Set the body to the json encoded string
        [networkRequest setHTTPBody:send]; 
        //Starts async request
        networkConnection = [[NSURLConnection alloc] initWithRequest:networkRequest delegate:self];
        //Successfully created, we are off
        if(networkConnection)
        {
            [networkConnectionsAndData setValue:[[NSMutableArray alloc] initWithObjects:uuid,
                                                 [[NSNumber alloc] initWithInt:op], [[NSNumber alloc] initWithInt:dataType], [[NSMutableData alloc] init], (data ? [data objectAtIndex:i] : [NSNull null]), nil]
                                         forKey:[networkConnection description]];
        }
        else    //Failed to conn ect
        {
            NSLog(@"Failed to create NSURLConnection");
            return NO;
        }
    }
    [[self networkOperationAndConnections] setObject:[[NSMutableDictionary alloc] initWithObjectsAndKeys:[[NSMutableArray alloc] initWithObjects:connections, nil], @"connections", [[NSMutableArray alloc] init], @"errors", nil]
                                              forKey:uuid];
    return YES;
}

Словари используются для отслеживания коррелирующих данных с каждым NSURLConnection, а также для объединения NSURLConnections вместе в одну группу для определения окончательного успеха или сбоя всей операции.

Обновление

AFNetworking был ключевым в завершении этого проекта. Он не только существенно очистил код, но и разрешил все проблемы с потоками, унаследованные при отправке большого количества запросов. Не говоря уже о AFNetworking, я мог объединить все запросы в одну операцию. Использование блоков, как в случае использования AFNetworking, было намного более чистым и лучшим решением, чем стандартные делегаты для NSURLConnections.

1 Ответ

5 голосов
/ 28 января 2012

Вам обязательно нужно разрешить NSURLRequest / Connection работать в другом потоке.(Не основная тема!)

Отредактировано для ясности **: Я заметил ваш комментарий к "//Starts async request" и хотел убедиться, что вы поняли, что ваш звонок там не тот, который вы ожидаете оттипичная "асинхронная" функция.На самом деле он просто запускает запрос синхронно, но, поскольку это веб-запрос, он ведет себя асинхронно.Вы хотите разместить эти запросы в другом потоке для полного асинхронного поведения.

Все остальное в стороне, я действительно советую покопаться в пример сетевого проекта Apple здесь: MVCNetworking

Что касается конкретных вопросов по вашему вопросу, есть параспособы сделать это.

  1. Одним из них является предотвращение немедленного запуска соединения с использованием initWithRequest:<blah> delegate:<blah> startImmediately:FALSE, а затем планирование NSURLConnection экземпляров в цикле выполнения другого потока с использованием: scheduleInRunLoop: forMode:
    • (Примечание. После этого вам необходимо отключить соединение, позвонив по команде start - лучше всего это сделать с помощью NSOperation + NSOperationQueue.)
  2. или использоватьэтот статический метод в NSURLConnection для создания / запуска соединения вместо выполнения alloc / init: sendAsynchronousRequest: queue: завершениеHandler:
    • (Примечание: этот подход выполняет в значительной степенито же самое, что и выше, но запутывает детали и берет часть контроля из ваших рук.)

Если честно, моих быстрых ответов, приведенных выше, будет недостаточно, чтобы закончить этот видпроекта, и вам нужно будет провести небольшое исследование, чтобы заполнить пробелы, особенно для NSOperationQueue, и в этом вам поможет проект MVCNetworking.

Сетевые подключения непростыst - Вы можете отключить и отключить ваши соединения, даже если они работают в фоновом потоке, просто пытаясь выполнять слишком много работы одновременно!Я бы серьезно пересмотрел бы открытие нескольких тысяч NSURLConnections одновременно, и использование NSOperationQueue поможет обойти это.

ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ: Вот сторонняя библиотека, которая может сделать ваши сетевые приключенияменее болезненный:

...