Многочисленная задача DataSask для URLSession при асинхронном вызове с обработчиком завершения приводит к увеличению объема памяти - PullRequest
0 голосов
/ 06 февраля 2019

Я разрабатываю проект загрузки в Swift.Я беру очень большие файлы (видео, изображение размером более 500 МБ) с помощью imagepickercontroller и делю этот файл на куски размером 1 МБ.Затем я отправляю эти куски на удаленный сервер и делаю их дефрагментацию на сервере, и я показываю этот файл пользователю.

У меня нет проблем, если размер файла меньше 300 МБ.Но после этого размера память слишком сильно возрастает и происходит сбой приложения.На самом деле, в каждом случае использование памяти увеличивается, но сбоев нет.

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

let url:URL = URL(string: "\(addressPrefix)UploadFile")!
let session = URLSession.shared
let request = NSMutableURLRequest(url: url)
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
request.httpMethod = "POST"

let bodyData = "\(metaDataID)~\(chunkIndex)~\(chunkSize)~\(chunkHash)~\(wholeTicket)~\(fileDataString)"

request.httpBody = bodyData.data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue));
request.timeoutInterval = .infinity

let task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
    guard let _:Data = data, let _:URLResponse = response, error == nil else {
       var attemptCounter = 1
       if attemptCounter <= 3 {
            completion("\(attemptCounter).attempt",chunkSize, error)
            attemptCounter += 1
        }
         return
     }
    let jsonStr = String(data: data!, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue))
    completion(jsonStr, chunkSize, error) 
 SingletonConnectionManager.sharedConnectionDataManager.dataTasks["uploadFile"] = nil 
})  
SingletonConnectionManager.sharedConnectionDataManager.dataTasks["uploadFile"] = task 
task.resume()

--- Я вызываю эту задачу URLSession из этой функции в контроллере табличного представления

 tmpConnection.uploadFile(chunk, metaDataID!, chunkIndex: chunkIndex, completion: {(result, chunkSize, error) in
   // I want to enter immediately when 'uploadFile' get called })

1 Ответ

0 голосов
/ 07 февраля 2019

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

Проблема здесь в том, что вы полностью забиваете сеанс, запуская слишком много задач одновременно.В NSURLSession есть известная ошибка, из-за которой он начинает разваливаться, когда вы создаете большое количество задач в одном сеансе за раз.Когда вы получаете слишком много задач в сеансе, IIRC, сеанс полностью прекращает обратный вызов, и в основном сеанс становится непригодным для использования.(Есть еще один вопрос переполнения стека, в котором это обсуждалось пару лет назад, хотя я не могу найти его прямо сейчас.)

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

Единственный способ решить эту проблему - перестать добавлять все запросы в сеанс.все сразу.Сначала запустите несколько заданий (максимум для восьми частей или около того), а затем дождитесь отправки следующей части, пока одна из предыдущих частей не завершится или не выполнится ошибка.Этот подход не только предотвратит блокирование NSURLSession, но также запретит выделять безумный объем памяти для хранения всех объектов NSData тела запроса, которые в настоящее время все одновременно находятся в оперативной памяти.

Я предлагаю сохранить NSMutableArray объекта NSNumber, представляющего каждый неотправленный кусок.Таким образом, вы знаете, что еще нужно отправить, и вы можете просто вернуться к 8 и извлечь первые 8 номеров и отправить куски с этими номерами.Когда запрос завершится успешно, извлеките следующий номер из массива и отправьте кусок с этим номером.

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

...