iOS: почему мои большие файлы не конвертируются с помощью NSData (contentsOfFile: options :)?Домен ошибки = код NSCocaErrorDomain = 256 - PullRequest
0 голосов
/ 13 декабря 2018

Я пытаюсь конвертировать видео с NSData, оно хорошо работает с небольшими видео или 100 МБ, но мои большие файлы (4,44 ГБ) не отправляются ...

   var video_data: NSData?
    do {
        video_data = try NSData(contentsOfFile: (videoPath), options: NSData.ReadingOptions.alwaysMapped)
    } catch let error as NSError {
        video_data = nil
        return
    }

Как я могу поместить большие файлыв NSData?

Error Domain=NSCocoaErrorDomain Code=256 "Impossible d’ouvrir le fichier « D9C7DABF-4BE3-4105-8D76-AA92B1D1502E_video.notsend »." UserInfo={NSFilePath=/var/mobile/Containers/Data/Application/EAE9B4C4-BE6B-490C-BEE7-381B2DF27CC9/Library/LEADS/D9C7DABF-4BE3-4105-8D76-AA92B1D1502E_video.notsend, NSUnderlyingError=0x283be1380 {Error Domain=NSPOSIXErrorDomain Code=12 "Cannot allocate memory"}}

Есть идеи?

Заранее спасибо.

РЕДАКТ. 1: ПАРАМЕТРЫ ДЛЯ ОТПРАВКИ:
Вот вся функция.Мне нужны все эти параметры для отправки на мой сервер.Мне нужно отправить eventId, contactId, тип и файл в значение данных.Проблема в том, что у меня ошибка, я не знаю, как поместить файл 4.44Go в данные с InputStream.

 func uploadVideo(_ videoPath: String, fileName: String, eventId: Int, contactId: Int, type: Int, callback: @escaping (_ data:Data?, _ resp:HTTPURLResponse?, _ error:NSError?) -> Void)
    {
        var video_data: Data
        video_data = self.getNextChunk(urlOfFile: NSURL(string: videoPath)! as URL)!

        let WSURL:String =  "https://" + "renauldsqffssfd3.sqdfs.fr/qsdf"

        let requestURLString = "\(WSURL)/qsdfqsf/qsdf/sdfqs/dqsfsdf/"
        let url = URL(string: requestURLString)
        let request = NSMutableURLRequest(url: url!)
        request.httpMethod = "POST"

        let boundary = generateBoundaryString()
        request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
        request.setValue("Keep-Alive", forHTTPHeaderField: "Connection")

        let body = NSMutableData()
        let mimetype = "video/mp4"

        //define the data post parameter
        body.append("--\(boundary)\r\n".data(using: String.Encoding.utf8)!)
        body.append("Content-Disposition:form-data; name=\"eventId\"\r\n\r\n".data(using: String.Encoding.utf8)!)
        body.append("\(eventId)\r\n".data(using: String.Encoding.utf8)!)

        body.append("--\(boundary)\r\n".data(using: String.Encoding.utf8)!)
        body.append("Content-Disposition:form-data; name=\"contactId\"\r\n\r\n".data(using: String.Encoding.utf8)!)
        body.append("\(contactId)\r\n".data(using: String.Encoding.utf8)!)

        body.append("--\(boundary)\r\n".data(using: String.Encoding.utf8)!)
        body.append("Content-Disposition:form-data; name=\"type\"\r\n\r\n".data(using: String.Encoding.utf8)!)
        body.append("\(type)\r\n".data(using: String.Encoding.utf8)!)

        body.append("--\(boundary)\r\n".data(using: String.Encoding.utf8)!)
        body.append("Content-Disposition:form-data; name=\"file\"; filename=\"\(fileName)\"\r\n".data(using: String.Encoding.utf8)!)
        body.append("Content-Type: \(mimetype)\r\n\r\n".data(using: String.Encoding.utf8)!)
        body.append(video_data)
        body.append("\r\n".data(using: String.Encoding.utf8)!)

        body.append("--\(boundary)--\r\n".data(using: String.Encoding.utf8)!)

        request.httpBody = body as Data

        let configuration = URLSessionConfiguration.default
        let session = URLSession(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)

        let task = session.uploadTask(with: request as URLRequest, from: body as Data) { loc, resp, err in
            if (resp != nil)
            {
                let status = (resp as! HTTPURLResponse).statusCode
            }
            callback(loc, resp as? HTTPURLResponse, err as NSError?)
        }

        task.resume()
}

  public func getNextChunk(urlOfFile: URL) -> Data?{
        if inputStream == nil {
            inputStream = InputStream(url: urlOfFile)!
            inputStream!.open()
        }
        var buffer = [UInt8](repeating: 0, count: 1024*1024)
        let len = inputStream!.read(&buffer, maxLength: 1024*1024)
        if len == 0 {
            return nil
        }
        return Data(buffer)
    }

РЕДАКТИРОВАТЬ 2: ДОПОЛНЕНИЕ К РЕШЕНИЮ:

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

  func sizeOfFileAtPath(path: String) -> UInt64
        {
            var fileSize : UInt64

            do {
                //return [FileAttributeKey : Any]
                let attr = try FileManager.default.attributesOfItem(atPath: path)
                fileSize = attr[FileAttributeKey.size] as! UInt64

                //if you convert to NSDictionary, you can get file size old way as well.
                let dict = attr as NSDictionary
                fileSize = dict.fileSize()
                            return fileSize

            } catch {
                print("Error: \(error)")
            }

            return 0
        }

    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()

/*-------BEGIN ADDITION TO THE CODE---------*/
        //check the size
        let temporaryFileSize = self.sizeOfFileAtPath(path: payloadFileURL.relativePath)
        let originalFileSize = self.sizeOfFileAtPath(path: videoFileURL.relativePath)

        if (temporaryFileSize < originalFileSize || temporaryFileSize == 0)
        {
            let alert = UIAlertView()
            alert.title = "Alert"
            alert.message = "There is not enough space on the disk."
            alert.addButton(withTitle: "Ok")
            alert.show()

            do {
                try FileManager.default.removeItem(at: payloadFileURL)
            } catch let error as NSError {
                print("Error: \(error.domain)")
            }
        }  
/*-------END ADDITION TO THE CODE---------*/

        return payloadFileURL
    }

Ответы [ 3 ]

0 голосов
/ 13 декабря 2018

Согласно документации Apple, вы можете использовать NSData(contentsOf:options:) для «синхронного чтения коротких файлов», поэтому он не должен обрабатывать файл размером 4 ГБ.Вместо этого вы можете использовать InputStream и инициализировать его URL-адресом с указанием пути к файлу.

0 голосов
/ 14 декабря 2018

При работе с такими крупными активами вы не должны использовать DataNSData) целиком.Итак:

  • прочитайте видео, используя 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
    }
}
0 голосов
/ 13 декабря 2018

В области перехвата у вас есть объект ошибки, это ваш ответ.

UPD: я предположил эту ошибку, и правильной причиной является Code=12 "Cannot allocate memory"

Вы можете попытаться разделить как- Правильно ли задан вызов: maxLength: один раз для каждого NSStreamEventHasBytesAvailable?

...