iOS: 为什么我的大文件没有用 NSData(contentsOfFile: options:) 转换?错误域=NSCocaErrorDomain 代码=256
iOS: Why my big files are not converted with NSData(contentsOfFile: options:)? Error Domain=NSCocaErrorDomain Code=256
我尝试使用 NSData 转换视频,它适用于小视频或 100mb,但我的大文件 (4.44Gb) 未发送...
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、类型和文件。问题是我有一个错误,我不知道如何使用 InputStream 将 4.44Go 文件放入数据中。
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:
解决方案的补充:
上面的Rob解决方案很完美。我只是在磁盘上添加了一个space的控件,如果临时文件无法复制,则提示,如果不完整,则将其删除,最后将问题告知用户。
事实上,如果没有该控制,即使文件不完整,应用程序也会尝试将文件发送到服务器...
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
}
在捕获区你有一个错误对象,这就是你的答案。
UPD:我认为是这个错误,正确的原因是 Code=12 "Cannot allocate memory"
你可以试试这样拆分 -
根据 Apple 文档,您可以使用 NSData(contentsOf:options:)
到 "read short files synchronously",因此它不能处理 4 GB 的文件。相反,您可以使用 InputStream
并使用带有文件路径的 URL 对其进行初始化。
在处理这么大的资产时,您希望完全避免使用 Data
(和 NSData
)。所以:
- 使用
InputStream
; 阅读视频
- 使用
OutputStream
将请求正文写入另一个文件;和
- 将该负载作为文件上传,而不是设置请求的
httpBody
;和
- 确保事后清理,删除临时负载文件。
所有这些都避免了一次将整个资产加载到内存中,并且您的峰值内存使用量将远低于使用 Data
时的峰值内存使用量。这也确保了这不太可能因为缺少 RAM 而失败。
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 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 GB 的视频可能需要很长时间,以至于用户可能不愿意离开应用 运行 并让上传完成;即使您的应用不再 运行,后台会话也会让上传完成;但后台上传需要基于文件的任务,而不是依赖请求的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
}
}
我尝试使用 NSData 转换视频,它适用于小视频或 100mb,但我的大文件 (4.44Gb) 未发送...
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、类型和文件。问题是我有一个错误,我不知道如何使用 InputStream 将 4.44Go 文件放入数据中。
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: 解决方案的补充:
上面的Rob解决方案很完美。我只是在磁盘上添加了一个space的控件,如果临时文件无法复制,则提示,如果不完整,则将其删除,最后将问题告知用户。
事实上,如果没有该控制,即使文件不完整,应用程序也会尝试将文件发送到服务器...
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
}
在捕获区你有一个错误对象,这就是你的答案。
UPD:我认为是这个错误,正确的原因是 Code=12 "Cannot allocate memory"
你可以试试这样拆分 -
根据 Apple 文档,您可以使用 NSData(contentsOf:options:)
到 "read short files synchronously",因此它不能处理 4 GB 的文件。相反,您可以使用 InputStream
并使用带有文件路径的 URL 对其进行初始化。
在处理这么大的资产时,您希望完全避免使用 Data
(和 NSData
)。所以:
- 使用
InputStream
; 阅读视频
- 使用
OutputStream
将请求正文写入另一个文件;和 - 将该负载作为文件上传,而不是设置请求的
httpBody
;和 - 确保事后清理,删除临时负载文件。
所有这些都避免了一次将整个资产加载到内存中,并且您的峰值内存使用量将远低于使用 Data
时的峰值内存使用量。这也确保了这不太可能因为缺少 RAM 而失败。
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 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 GB 的视频可能需要很长时间,以至于用户可能不愿意离开应用 运行 并让上传完成;即使您的应用不再 运行,后台会话也会让上传完成;但后台上传需要基于文件的任务,而不是依赖请求的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
}
}