NSURLConnection нуждается в NSRunLoop для выполнения?

Я пытаюсь получить содержимое URL-адреса из метода с именем connect. Он считывает параметры конфигурации и пытается получить URL-адрес. Похоже, для выполнения требуется цикл выполнения. Я думал, что это просто будет выполнено один раз и будет сделано. Кажется, мне не нужно, чтобы этот цикл запуска выполнялся по какой-либо причине, кроме как для получения этого URL-адреса один раз при каждом вызове подключения. Есть ли лучший способ сделать это?

- (BOOL) connect 
{
    // read serverName and eventId from preferences
    NSMutableArray* preferences;
    preferences = [NSMutableDictionary dictionaryWithContentsOfFile:@"/tmp/wt.plist"];

    // if these values aren't nil or blank
    if ([preferences valueForKey:@"serverAddress"] && [preferences valueForKey:@"eventId"]) {

        [self setServerAddress:@"172.16.28.210"];
        [self setEventId:@"OT-3037009"];
    } else{
        // return some kind of error message
    }

    NSLog(@"VideoDataURL: %@", [self getVideoDataURL]);

    // grab the URL and query the server

    NSURL *myURL = [NSURL URLWithString:@"http://localhost"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:myURL
                                                           cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
                                                       timeoutInterval:2];

    __unused NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];

    [[NSRunLoop currentRunLoop] run];

    return TRUE;
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    // This method is called when the server has determined that it
    // has enough information to create the NSURLResponse.

    // It can be called multiple times, for example in the case of a
    // redirect, so each time we reset the data.

    // receivedData is an instance variable declared elsewhere.
    [incomingData setLength:0];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    /* 
     * Called each time a chunk of data arrives
     */

    NSLog(@"received %lu bytes", [data length]);

    // Create a mutable data if it doesn't already exist
    if (!incomingData) {
        incomingData = [[NSMutableData alloc] init];
    }

    [incomingData appendData:data];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 
{
    /*
     * Called if the connection fails
     */

    NSLog(@"connection failed: %@", [error localizedDescription]);  
    incomingData = nil;
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection 
{
    /*
     * Called when the last chunk has been processed
     */

    NSLog(@"Got it all!");

    NSString *string = [[NSString alloc] initWithData:incomingData encoding: NSUTF8StringEncoding];

    incomingData = nil;

    NSLog(@"response is: %@", string);
}

person Adam    schedule 10.05.2012    source источник
comment
В документации говорится: Загрузка запускается сразу после получения сообщения initWithRequest:delegate:. Кажется, это не тот случай. Мне, должно быть, не хватает чего-то, что заставляет мой NSURLConnection ждать цикла выполнения.   -  person Adam    schedule 10.05.2012
comment
Я все равно добавляю явный вызов start. Согласно документации, это может быть ненужным, но я думаю, что это не помешает!   -  person Ash Furrow    schedule 10.05.2012
comment
Проблема заключалась в том, что мое приложение является приложением командной строки, которое по умолчанию не имеет цикла выполнения. Мне пришлось запустить один, чтобы заставить его работать должным образом.   -  person Adam    schedule 12.05.2012


Ответы (3)


Если вы начинаете соединение с основного потока, да, это правильно. Я просто использую GCD для создания и запуска соединения в основном потоке, чтобы не думать о циклах выполнения.

dispatch_async(dispatch_get_main_queue(), ^{
    NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
    [conn start];
});

Работает из любого места, и вам не нужно отслеживать циклы выполнения или потоки.

person Ash Furrow    schedule 10.05.2012
comment
Боже мой, я пытался выяснить причину того, что мой NSURLConnection не получает обратные вызовы метода делегата весь день... спасибо - person Jason; 31.05.2012
comment
Кажется, не работает, по-прежнему не получается - (void) connectionDidFinishLoading: (NSURLConnection *) обратный вызов соединения... - person jjxtra; 01.08.2012
comment
Вы отвечаете на другие методы делегата? Возможно ли, что соединение не работает? Это может произойти, например, из-за того, что система не знает, как обрабатывать URL-адрес. Попробуйте поместить это в синхронный запрос, чтобы увидеть, работает ли он там, а если нет, то он не будет работать и с асинхронными запросами. - person Ash Furrow; 01.08.2012
comment
Разве запуск этого на основном (т.е. потоке пользовательского интерфейса) не является плохой идеей? Это может работать для коротких запросов, но для более длинных, которые будут загружать много данных, вы, вероятно, захотите сохранить их в отдельном потоке/цикле выполнения. - person kevlar; 18.02.2013
comment
Попробовал, по крайней мере, теперь вызывается connectionDidFinishLoading. Но либо параметры, которые я отправил, не соблюдаются, либо веб-данные возвращают нежелательный результат. Мой веб-сервис не возвращает правильное значение, где-то не так. - person TPG; 13.09.2015
comment
Это устранило проблему с тем, что соединение не было установлено для меня, спасибо! - person Marcus Roberts; 10.10.2016

Я бы убедился, что вы вручную сообщаете runLoop рабочего потока, в котором вы создаете соединение, для запуска до тех пор, пока соединение не будет завершено. Если вы этого не сделаете, Cocoa автоматически переведет поток в спящий режим, поэтому ваши методы делегата, похоже, никогда не получат ответа.

// The following is delcared within the scope of a NSThread, NSOperation, or GCD Block
//
// The NSURLConnection object and it's initialization/start methods
// |
// V
// Above the method call to the thread's runLoop.
//
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:<a time interval>];
// I use a minute, or 60.0, before I manually cancel the connection
// and send an error message back to the caller.

^Это метод, который вы должны вызывать после создания соединения. Это должно дать вам результаты, которые вы ищете.

Помните об этой структуре при создании асинхронных вызовов/задач сервера:

Нить

RunLoop

Задача

Каждой асинхронной задаче нужен свой RunLoop, чтобы события постоянно опрашивались в течение всего времени ее существования. Каждый RunLoop должен существовать в рамках потока, чтобы связанные с ним задачи могли выполняться.

Вы можете создать асинхронную сетевую задачу вручную, используя NSThread/NSRunLoop/NSURLConnection, Grand Central Dispatch или даже NSOperation/NSOperationQueue. Я предпочитаю Операции/Очереди, потому что я чувствую, что они более объектно-ориентированы, а программирование на основе блоков может быть довольно сложным (это структура, в которой вы бы написали свой запрос, если бы вы просто использовали вызовы «dispatch_»). Когда все сводится к этому, напишите свою модель потоков, основываясь на своих предпочтениях в программировании. Каждый подход даст вам один и тот же результат.

Кроме того, не используйте dispatch_sync, если только вы не выполняете ту часть задачи, которая требует синхронизации, иначе вы заблокируете пользовательский интерфейс. Мастерство RunLoops необходимо для создания действительно асинхронной модели для вашего приложения.

Удачи!

P.S. NSURLConnection уже создан для асинхронной работы, но для создания действительно асинхронной сетевой модели ваше приложение должно создавать соединения в потоках, отдельных от основного потока.

person user298261    schedule 13.08.2013
comment
Не могли бы вы немного расширить свой ответ с помощью кода? Я получаю gcd и часть делегата NSURLConnection. Как в это вписать NSRunLoop, мне непонятно. - person user965972; 01.03.2015
comment
Код, который я написал, должен быть размещен под асинхронным вызовом URL, если это соединение вызывается где-то помимо MainThread. Если вы этого не сделаете, вы рискуете тем, что Cocoa переведет поток в спящий режим после создания соединения и потенциально пропустит обновления состояния этого соединения. В результате вы не получите данные, которые намеревались получить/опубликовать/обновить и т. д. Я обновлю свой пост в соответствии с вашей просьбой. - person user298261; 04.06.2015

Я не думаю, что синхронные запросы нуждаются в цикле выполнения:

NSURLResponse* response = nil;
NSData* data = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:&response error:nil];
person Arlen Anderson    schedule 10.05.2012
comment
Это правда, но вопрос конкретно касается асинхронных вызовов, которые намного мощнее, чем метод sendSynchronousRequest:returningResponse:error:. - person Ash Furrow; 10.05.2012