Рассчитать md5 с NSURLSessionDownloadTask для очень большого файла

NSURLConnection можно использовать для вычисления md5 на лету:

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)theData
    // theData is a small piece

NSURLSessionDownloadTask - это "обновление" NSURLConnection. Но как мы можем проверить md5 без повторного чтения всего файла после его загрузки? Его интерфейс похож на:

NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request
                                                     completionHandler:
^(NSURL *location, NSURLResponse *response, NSError *error) {
    // the whole file is downloaded and saved at location.
}];

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

2 ответа

Решение

Если вы хотите, чтобы данные поступали небольшими кусочками NSData, которые вы можете проверить и добавить к большему количеству NSMutableData, как вы это делали с connection:didReceiveData:, запросите задачу данных вместо задачи загрузки.

Ты звонишь dataTaskWithRequest:введите делегата и запустите задачу resume) - и делегат получает URLSession:dataTask:didReceiveData:Точно так же, как в старые времена NSURLConnection.

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

- (NSURLSession*) configureSession {
    NSURLSessionConfiguration* config =
    [NSURLSessionConfiguration ephemeralSessionConfiguration];
    config.allowsCellularAccess = NO;
    NSURLSession* session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    return session;
}

- (IBAction) doHTTP: (id) sender {
    if (!self.session)
        self.session = [self configureSession];

    NSString* s = // some URL string
    NSURL* url = [NSURL URLWithString:s];
    NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url];
    NSURLSessionDataTask* task = [[self session] dataTaskWithRequest:req];
    self.task = task;
    [task resume];
}

-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    NSLog(@"received %lu bytes of data", (unsigned long)data.length);
    // do something with the data here!
}

-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    NSLog(@"completed; error: %@", error);
}

Как сказал Мэтт, вы можете использовать задачу с данными, которая позволяет вам легко видеть данные по мере их загрузки.

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

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

Во-первых, я думаю, что API не работает. Делегаты должны предоставить хотя бы одну из этих двух вещей. Если вы согласны, подайте радар. Делегат должен предоставить временный файл (гораздо менее предпочтительный - я думаю, что он должен оставаться непрозрачным), или он должен предоставить NSData что пишется в URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite: - это правильный ответ.

Во всяком случае, если вы хотите использовать недокументированный, неофициальный подход...

Временные файлы хранятся в Library/Caches/com.apple.nsnetworkd/ так что вы можете легко найти там и определить, какие файлы используются в качестве временного места назначения.

Или вы можете, опять же неофициально, определить временный файл, отменив загрузку с помощью cancelByProducingResumeData: а затем разархивировать блок данных резюме - блок данных резюме в настоящее время является заархивированным словарем - и получить путь к файлу из словаря. Затем вы можете возобновить загрузку, зная, какой временный файл используется для загрузки.

Во всяком случае, когда у вас есть файл, внутри вашего URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite: затем вы можете просто прочитать последний записанный фрагмент из файла.

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

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

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

Вам придется сделать то же самое для URLSession:downloadTask:didFinishDownloadingToURL: чтобы получить последний кусок данных.

Другие вопросы по тегам