одновременные фоновые загрузки на iphone - PullRequest
5 голосов
/ 25 января 2010

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

У меня есть функция addDonwload, которая добавляет URL-адрес в список URL-адресов для загрузки и проверяет наличие свободного слота для загрузки. Если он есть, загрузка начинается немедленно. Когда одна из загрузок заканчивается, я выбираю первый список URL-адресов и начинаю новую загрузку.

Я использую NSURLConnection для скачивания, вот код

- (bool) TryDownload:(downloadInfo*)info
{
    int index;
    @synchronized(_asyncConnection)
    {
        index = [_asyncConnection indexOfObject:nullObject];
        if(index != NSNotFound)
        {
            NSLog(@"downloading %@ at index %i", info.url, index);
            activeInfo[index] = info;
            NSURLRequest *request = [NSURLRequest requestWithURL:info.url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:15];

            [_asyncConnection replaceObjectAtIndex:index withObject:[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:TRUE]];
            //[[_asyncConnection objectAtIndex:i] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];           

            return true;
        }
    }

    return false;
}

- (void)connectionDidFinishLoading:(NSURLConnection*)connection
{
  [self performSelectorOnMainThread:@selector(DownloadFinished:) withObject:connection waitUntilDone:false];
}

- (void)DownloadFinished:(id)connection
{
    NSInteger index = NSNotFound;
    @synchronized(_asyncConnection)
    {
        index = [_asyncConnection indexOfObject:(NSURLConnection*)connection];
    }

    [(id)activeInfo[index].delegate performSelectorInBackground:@selector(backgroundDownloadSucceededWithData:) withObject:_data[index]];
    [_data[index] release];
    [activeInfo[index].delegate release];
    @synchronized(_asyncConnection)
    {
        [[_asyncConnection objectAtIndex:index] release];
        [_asyncConnection replaceObjectAtIndex:index withObject:nullObject];            
    }
    @synchronized(downloadQueue)
    {
        [downloadQueue removeObject:activeInfo[index]];
        [self NextDownload];
    }
}

- (void)NextDownload
{
    NSLog(@"files remaining: %i", downloadQueue.count);
    if(downloadQueue.count > 0)
    {
        if([self TryDownload:[downloadQueue objectAtIndex:0]])
        {
            [downloadQueue removeObjectAtIndex:0];
        }
    }
}

_asyncConnection - это мой массив слотов для загрузки (NSURLConnections) downloadQueue - список URL для загрузки

Что происходит, в начале все работает нормально, но после нескольких загрузок мои соединения начинают исчезать. Загрузка начинается, но соединение: didReceiveResponse: никогда не вызывается. В консоли вывода есть одна вещь, которую я не понимаю, которая может немного помочь. Нормально есть что-то вроде 2010-01-24 21: 44: 17.504 appName [3057: 207] до моих сообщений NSLog. Я думаю, что число в квадратных скобках - это какое-то приложение: идентификатор потока? все работает нормально, пока есть одно и то же число, но через некоторое время "NSLog (@" загрузка% @ по индексу% i ", info.url, index);" сообщения начинают отличаться от второго номера. И когда это происходит, я прекращаю получать любые обратные вызовы для этого URL-соединения.

Это сводило меня с ума, потому что у меня строгие сроки и я не могу найти проблему. У меня нет большого опыта работы с iphone dev и многопоточными приложениями. Я пробовал разные подходы, поэтому мой код немного запутан, но я надеюсь, что вы увидите, что я пытаюсь сделать здесь:)

Кстати, кто-нибудь из вас знает о существующем классе / lib, который я мог бы использовать, что также было бы полезно. Я хочу параллельные загрузки с возможностью o динамически добавлять новые файлы для загрузки (поэтому инициализация загрузчика в начале со всеми URL мне не нужна)

Ответы [ 5 ]

2 голосов
/ 30 января 2010

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

Вместо того, чтобы вдаваться в подробности, я задам следующий вопрос: Вы делаете это в какой-то фоновой ветке? Зачем? IIRC NSURLConnection уже выполняет загрузку в фоновом потоке и вызывает ваш делегат в потоке, в котором был создан NSURLConnection (например, в идеале, ваш основной поток).

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

Дальнейшее предложение: вместо того, чтобы пытаться поддерживать параллельное позиционирование в двух массивах (и некоторый схематичный код в приведенном выше, относящемся к этому), создайте один массив и получите объект, который содержит как NSURLConnection, так и объект, представляющий результат. Затем вы можете просто освободить экземпляр соединения var, когда соединение будет установлено. И родительский объект (и, следовательно, данные), когда вы закончите с данными.

1 голос
/ 04 февраля 2010

Я рекомендую вам взглянуть на это: http://allseeing -i.com / ASIHTTPRequest /

Это довольно сложный набор классов с либеральными условиями лицензирования (тоже бесплатно).

Это может обеспечить множество функций, которые вы хотите.

0 голосов
/ 02 февраля 2010

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

Если вы обнаружите, что ваш параллельный подход запрещает выполнять всю обработку в главном потоке, рассмотрите возможность наличия объектов посредника между кодом загрузки основного потока и NSURLConnection. Используя этот подход, вы создадите экземпляр своего менеджера и получите возможность синхронно использовать NSURLConnection в фоновом потоке. Затем этот менеджер полностью обрабатывает загрузку и передает результат обратно своему делегату основного потока, используя метод executeSelectorOnMainThread: withObject:. Каждая загрузка - это всего лишь случай создания нового объекта менеджера, когда у вас есть свободный слот, и его установка.

0 голосов
/ 26 января 2010

Вы используете connection:didFailWithError:? Может быть тайм-аут, мешающий успешному завершению загрузки.

Попробуйте избавиться от блоков @synchronized и посмотрите, что произойдет.

Строка в квадратных скобках, как вы уже догадались, является идентификатором нити. Так что, может быть, вы заперли в @synchronized. На самом деле, я не вижу причины для переключения потока - весь проблемный код должен выполняться в основном потоке (performSelectorOnMainThread) ...

Во всяком случае, нет необходимости использовать как @synchronized, так и performSelectorOnMainThread.

Кстати, я не видел NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; строку. Где вы инициируете соединение?

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

0 голосов
/ 25 января 2010

Этот фрагмент может быть источником ошибки: вы освобождаете объект, на который указывает указатель activeInfo[index].delegate, сразу после вызова асинхронного метода для этого объекта.

[(id)activeInfo[index].delegate performSelectorInBackground:@selector(backgroundDownloadSucceededWithData:) withObject:_data[index]];
[_data[index] release];
[activeInfo[index].delegate release];
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...