При работе с такими крупными активами вы не должны использовать Data
(и NSData
) целиком.Итак:
- прочитайте видео, используя
InputStream
; - запишите тело запроса в другой файл, используя
OutputStream
;и - загружать эту полезную нагрузку в виде файла вместо установки
httpBody
запроса;и - после этого обязательно очистите, удалив этот временный файл полезной нагрузки.
Все это позволяет избежать загрузки всего ресурса в память за раз, и ваше пиковое использование памяти будетнамного ниже, чем было бы, если бы вы использовали Data
.Это также гарантирует, что это вряд ли когда-либо даст сбой из-за нехватки ОЗУ.
func uploadVideo(_ videoPath: String, fileName: String, eventId: Int, contactId: Int, type: Int, callback: @escaping (_ data: Data?, _ resp: HTTPURLResponse?, _ error: Error?) -> Void) {
let videoFileURL = URL(fileURLWithPath: videoPath)
let boundary = generateBoundaryString()
// build the request
let request = buildRequest(boundary: boundary)
// build the payload
let payloadFileURL: URL
do {
payloadFileURL = try buildPayloadFile(videoFileURL: videoFileURL, boundary: boundary, fileName: fileName, eventId: eventId, contactId: contactId, type: type)
} catch {
callback(nil, nil, error)
return
}
// perform the upload
performUpload(request, payload: payloadFileURL, callback: callback)
}
enum UploadError: Error {
case unableToOpenPayload(URL)
case unableToOpenVideo(URL)
}
private func buildPayloadFile(videoFileURL: URL, boundary: String, fileName: String, eventId: Int, contactId: Int, type: Int) throws -> URL {
let mimetype = "video/mp4"
let payloadFileURL = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent(UUID().uuidString)
guard let stream = OutputStream(url: payloadFileURL, append: false) else {
throw UploadError.unableToOpenPayload(payloadFileURL)
}
stream.open()
//define the data post parameter
stream.write("--\(boundary)\r\n")
stream.write("Content-Disposition:form-data; name=\"eventId\"\r\n\r\n")
stream.write("\(eventId)\r\n")
stream.write("--\(boundary)\r\n")
stream.write("Content-Disposition:form-data; name=\"contactId\"\r\n\r\n")
stream.write("\(contactId)\r\n")
stream.write("--\(boundary)\r\n")
stream.write("Content-Disposition:form-data; name=\"type\"\r\n\r\n")
stream.write("\(type)\r\n")
stream.write("--\(boundary)\r\n")
stream.write("Content-Disposition:form-data; name=\"file\"; filename=\"\(fileName)\"\r\n")
stream.write("Content-Type: \(mimetype)\r\n\r\n")
if stream.append(contentsOf: videoFileURL) < 0 {
throw UploadError.unableToOpenVideo(videoFileURL)
}
stream.write("\r\n")
stream.write("--\(boundary)--\r\n")
stream.close()
return payloadFileURL
}
private func buildRequest(boundary: String) -> URLRequest {
let WSURL = "https://" + "renauldsqffssfd3.sqdfs.fr/qsdf"
let requestURLString = "\(WSURL)/qsdfqsf/qsdf/sdfqs/dqsfsdf/"
let url = URL(string: requestURLString)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.setValue("Keep-Alive", forHTTPHeaderField: "Connection")
return request
}
private func performUpload(_ request: URLRequest, payload: URL, callback: @escaping (_ data: Data?, _ resp: HTTPURLResponse?, _ error: Error?) -> Void) {
let session = URLSession(configuration: .default, delegate: self, delegateQueue: .main)
let task = session.uploadTask(with: request, fromFile: payload) { data, response, error in
try? FileManager.default.removeItem(at: payload) // clean up after yourself
if let response = response as? HTTPURLResponse {
let status = response.statusCode
}
callback(data, response as? HTTPURLResponse, error)
}
task.resume()
}
Кстати, загрузка этого файла также имеет то преимущество, что вы можете рассмотреть возможность использования фона URLSessionConfiguration
внекоторая будущая дата (т. е. загрузка видео объемом 4 ГБ, вероятно, займет так много времени, что пользователь может не захотеть оставить приложение запущенным и завершить загрузку; фоновые сеансы позволяют завершить загрузку, даже если ваше приложение больше не работает); но для фоновой загрузки требуются файловые задачи, не основанные на httpBody
запроса).
Это совсем другая проблема, выходящая за рамки здесь, но, надеюсь, вышеприведенное иллюстрирует ключевую проблему здесь, а именно: не используйте NSData
/ Data
при работе с активами, которые так велики.
Обратите внимание, что в приведенном выше примере используется следующее расширение для OutputStream
, включая метод записи строк в выходные потоки и добавления содержимого другого файла в поток:
extension OutputStream {
@discardableResult
func write(_ string: String) -> Int {
guard let data = string.data(using: .utf8) else { return -1 }
return data.withUnsafeBytes { (buffer: UnsafePointer<UInt8>) -> Int in
write(buffer, maxLength: data.count)
}
}
@discardableResult
func append(contentsOf url: URL) -> Int {
guard let inputStream = InputStream(url: url) else { return -1 }
inputStream.open()
let bufferSize = 1_024 * 1_024
var buffer = [UInt8](repeating: 0, count: bufferSize)
var bytes = 0
var totalBytes = 0
repeat {
bytes = inputStream.read(&buffer, maxLength: bufferSize)
if bytes > 0 {
write(buffer, maxLength: bytes)
totalBytes += bytes
}
} while bytes > 0
inputStream.close()
return bytes < 0 ? bytes : totalBytes
}
}