2012-05-15 4 views
4

У меня есть приложение, которое успешно использует синхронные методы для загрузки файлов (NSData initWithContentsOfURL и NSURLConnection sendSynchronousRequest), но теперь мне нужно поддерживать большие файлы. Это означает, что мне нужно бить поток на диск. Несмотря на то, что потоковая передача на диск и становление асинхронными должны быть полностью ортогональными концепциями, API Apple заставляет меня идти асинхронно для потоковой передачи.Как дождаться загрузки большого файла?

Чтобы быть ясным, мне поручено увеличить загрузку файлов, а не перестраивать все приложение, чтобы они были более асинхронными. У меня нет ресурсов. Но я признаю, что подходы, которые зависят от реорганизации, являются правильными и хорошими.

Так что, если я это сделать:

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

.. Я в конце концов didReceiveResponse и didReceiveData называется на себя. Отлично. Но, если я пытаюсь сделать это: не

NSURLConnection* connection = [ [ NSURLConnection alloc ] initWithRequest: request delegate: self startImmediately: YES ]; 
while(!self.downloadComplete) 
    [ NSThread sleepForTimeInterval: .25 ]; 

... didReceiveResponse и didReceiveData не называются. И я понял, почему. Как ни странно, асинхронная загрузка происходит в том же основном потоке, который я использую. Поэтому, когда я сплю основную нить, я тоже сплю, что делаю работу. Во всяком случае, я пробовал несколько разных способов достичь того, что я хочу здесь, включая указание NSURLConnection использовать другой NSOperationQueue и даже делать dispatch_async для создания соединения и запускать его вручную (я не вижу, как это не может работать - Я не должен был делать это правильно), но ничего не работает. Редактировать: Что я не делаю правильно, так это понимание того, как работают петли цикла, и что вам нужно запускать их вручную во вторичных потоках.

Каков наилучший способ подождать, пока файл не будет загружен?

Редактировать 3, рабочий код: Следующий код на самом деле работает, но дайте мне знать, если есть лучший способ.

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

dispatch_queue_t downloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
dispatch_async(downloadQueue, ^{ 
    self.connection = [ [ NSURLConnection alloc ] initWithRequest: request delegate: self startImmediately: YES ]; 
    [ [ NSRunLoop currentRunLoop ] run ]; 
}); 

while(!self.downloadComplete) 
    [ NSThread sleepForTimeInterval: .25 ]; 

код, выполняемый в новом потоке, который реагирует на подключение событий:

-(void)connection:(NSURLConnection*) connection didReceiveData:(NSData *)data { 
    NSUInteger remainingBytes = [ data length ]; 
    while(remainingBytes > 0) { 
     NSUInteger bytesWritten = [ self.fileWritingStream write: [ data bytes ] maxLength: remainingBytes ]; 
     if(bytesWritten == -1 /*error*/) { 
      self.downloadComplete = YES; 
      self.successful = NO; 
      NSLog(@"Stream error: %@", self.fileWritingStream.streamError); 
      [ connection cancel ]; 
      return; 
     } 
     remainingBytes -= bytesWritten; 
    } 
} 

-(void)connection:(NSURLConnection*) connection didFailWithError:(NSError *)error { 
    self.downloadComplete = YES; 
    [ self.fileWritingStream close ]; 
    self.successful = NO; 
} 

- (void)connectionDidFinishLoading:(NSURLConnection *)connection { 
    self.downloadComplete = YES; 
    [ self.fileWritingStream close ]; 
    self.successful = YES; 
} 
+0

Если это не проприетарный, мне бы очень хотелось увидеть код в функции didReceiveData. –

+0

@Thunder К счастью. Это в основном то, что я нашел здесь: http://stackoverflow.com/q/4002979/34290, но я также включил его в мой вопрос выше. –

+0

wow sweet! Огромное спасибо. = :-) –

ответ

5

... didReceiveResponse и didReceiveData никогда не вызываются. И у меня есть . Как ни странно, асинхронная загрузка происходит в тем же основным потоком, который я использую. Он не создает новый поток. Итак, , когда я сплю основную нить, я тоже сплю, что делаю работу .

Точно. Соединение управляется циклом запуска; если вы спите поток, цикл запуска останавливается, и это мешает вашему соединению выполнять свою работу.

Так что не делайте ничего особенного. Пусть приложение сидит там, при запуске цикла запуска. Возможно, на экране появится маленькая прядильщик, чтобы развлечь пользователя. Поговорите о своем бизнесе, если сможете. Если это вообще возможно, пусть пользователь продолжает использовать приложение. Ваш метод делегирования будет вызываться, когда соединение будет завершено, а затем вы сможете делать то, что вам нужно делать с данными.

Когда вы перемещаете свой код в фоновый поток, вам снова понадобится цикл запуска для подключения этого соединения. Таким образом, вы начнете создавать цикл запуска, планируете свое соединение, а затем просто возвращаетесь. Цикл выполнения будет продолжать работать, и ваш метод делегирования будет снова вызван, когда соединение завершится. Если поток завершен, вы можете остановить цикл выполнения и позволить потоку выйти. Вот и все.

Пример: Положим это на конкретные условия. Предположим, что вы хотите сделать несколько соединений, по одному за раз. Вставьте URL-адрес в изменчивый массив. Создайте метод, называемый (например) startNextConnection, что делает следующие вещи:

  • захватывает URL из массива (удаление его в процессе)

  • создает запрос на URL

  • начинается NSURLConnection

  • возвращение

Также необходимо реализовать необходимые методы NSURLConnectionDelegate, в частности connectionDidFinishLoading:. У этого метода выполните следующие действия:

  • копить данные где-то (записать его в файл, передать его другой поток для синтаксического анализа, безотносительно)

  • вызова startNextConnection

  • возвращение

Если ошибок не было, этого было бы достаточно, чтобы получить данные для всех URL-адресов в вашем списке. (Конечно, вам нужно, чтобы был достаточно умным, чтобы просто вернуться, когда список пуст.) Но ошибки происходят, поэтому вам придется подумать о том, как с ними бороться. Если соединение выходит из строя, вы хотите остановить весь процесс?Если это так, просто попробуйте использовать метод connection:didFailWithError:, но не звоните startNextConnection. Вы хотите перейти к следующему URL-адресу в списке, если есть ошибка? Затем получите ...didFailWithError: звонок startNextRequest.

Альтернатива: Если вы действительно хотите сохранить последовательную структуру вашего синхронного кода, так что у вас есть что-то вроде:

[self downloadURLs]; 
[self waitForDownloadsToFinish]; 
[self processData]; 
... 

, то вы должны будете сделать загрузку в другой чтобы вы могли заблокировать текущий поток. Если это то, что вы хотите, настройте поток загрузки с циклом запуска. Затем создайте соединение, используя -initWithRequest:delegate:startImmediately:, как вы делали, но передайте NO в последнем параметре. Используйте -scheduleInRunLoop:forMode:, чтобы добавить соединение к циклу запуска потока загрузки, а затем запустите соединение с помощью метода -start. Это дает вам возможность спать текущей нити. Попросите процедуру завершения делегирования соединения установить в вашем примере флаг, такой как флаг self.downloadComplete.

+0

Но мне нужно сделать что-то особенное. Если я ничего не сделаю, как только я вернусь от метода, который создал соединение (которое будет немедленно), следующий шаг - загрузить другой файл, а другой. Итак, в итоге я загружу сразу 100 файлов, чего я не хочу. –

+0

Так что не делайте следующий запрос до завершения первого подключения. Вы узнаете, когда это произойдет, потому что будет вызван ваш метод делегата. Фактически, вы можете использовать метод 'connectionDidFinishLoading:', чтобы захватить следующий URL-адрес и запустить другое соединение, поэтому следующее соединение начнется сразу же после завершения предыдущего. – Caleb

+0

«Не делайте следующий запрос до завершения первого соединения» - это мой весь вопрос. Как подождать? –

0

I думаю, что ваш sleepForInterval блокирует деятельность NSURLConnection -

Нет r Выполнение un loop происходит, когда поток заблокирован.

От NSThread documentation.


Я думаю, что вы, возможно, придется пересмотреть, как вы настраиваете вашу downloadComplete переменную. Подумайте о том, как использовать метод делегата connectionDidFinishLoading:connection, чтобы определить, когда загрузка завершена, а не ваш цикл + сон?

- (void)connectionDidFinishLoading:(NSURLConnection *)connection 
{   
    self.downloadComplete = YES; 

    // release the connection, and the data object 
    [connection release]; 
    [receivedData release]; 
} 

От NSURLConnection guide.

Вы можете использовать метод делегата connection:connection didFailWithError:error, чтобы убедиться, что имеете дело с ситуациями, когда загрузка не завершена.

+0

Я не думаю, что могу использовать sendAsynchronousRequest: queue: completionHandler: потому что он, кажется, дает вам весь файл в один большой кусок (NSData передается обработчику завершения). –

+0

Почему вы не хотите получать данные таким образом? –

+0

Потому что iPad заканчивается из ОЗУ и падает. Мне нужно передать это в файл, а не получить весь файл в один большой блок NSData, а затем записать его одним выстрелом. –

1

Я не решаюсь предоставить этот ответ, потому что другие правильны, что вы действительно должны структурировать приложение вокруг асинхронной модели. Тем не менее:

NSURLConnection* connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; 
NSString* myPrivateMode = @"com.yourcompany.yourapp.DownloadMode"; 
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:myPrivateMode]; 
[connection start]; 
while (!self.downloadComplete) 
    [[NSRunLoop currentRunLoop] runMode:myPrivateMode beforeDate:[NSDate distantFuture]]; 

Не делайте этого по основной теме. Ваше приложение так же вероятно прекращено для блокировки основного потока, как для загрузки слишком большого файла в память.

Кстати, учитывая, что вы загружаете файл вместо памяти, вам следует перейти от NSURLConnection к NSURLDownload.

+0

Я действительно рассматривал использование NSURLDownload, но я понимаю, что он недоступен в iOS. Кроме того, очень возможно, что ваш ответ будет работать для меня (и других людей, рассматривающих это), но я пришел к решению, основанному на том, что сказал Калеб, прежде чем я увидел ваш ответ, поэтому я не пробовал. –

+0

+1 Хорошее решение. Запуск циклов слишком часто пропускается. – Caleb