gpt4 book ai didi

ios - URLSession 下载任务比互联网连接慢得多

转载 作者:可可西里 更新时间:2023-11-01 04:37:32 30 4
gpt4 key购买 nike

我正在使用 URLSession 和 downloadTask 在前台下载文件。下载比预期慢得多。我发现的其他帖子解决了后台任务的问题。

let config = URLSessionConfiguration.default
config.httpMaximumConnectionsPerHost = 20
let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)

let request = URLRequest(url: url)
let completion: ((URL?, Error?) -> Void) = { (tempLocalUrl, error) in
print("Download over")
}
value.completion = completion
value.task = self.session.downloadTask(with: request)

我观察到网络使用量约为 150kb/s,而我设备上的速度测试报告连接速度为 5MB/s

=== 编辑

我可以确认编码分段下载(这做起来有点痛苦)可以大大加快速度。

最佳答案

如果这对任何人有帮助,这里是我的代码来加速下载。它将文件下载拆分为多个文件部分下载,从而更有效地使用可用带宽。必须这样做仍然感觉不对...

最终的用法是这样的:

// task.pause is not implemented yet
let task = FileDownloadManager.shared.download(from:someUrl)
task.delegate = self
task.resume()

代码如下:

/// Holds a weak reverence
class Weak<T: AnyObject> {
weak var value : T?
init (value: T) {
self.value = value
}
}

enum DownloadError: Error {
case missingData
}

/// Represents the download of one part of the file
fileprivate class DownloadTask {
/// The position (included) of the first byte
let startOffset: Int64
/// The position (not included) of the last byte
let endOffset: Int64
/// The byte length of the part
var size: Int64 { return endOffset - startOffset }
/// The number of bytes currently written
var bytesWritten: Int64 = 0
/// The URL task corresponding to the download
let request: URLSessionDownloadTask
/// The disk location of the saved file
var didWriteTo: URL?

init(for url: URL, from start: Int64, to end: Int64, in session: URLSession) {
startOffset = start
endOffset = end

var request = URLRequest(url: url)
request.httpMethod = "GET"
request.allHTTPHeaderFields?["Range"] = "bytes=\(start)-\(end - 1)"

self.request = session.downloadTask(with: request)
}
}

/// Represents the download of a file (that is done in multi parts)
class MultiPartsDownloadTask {

weak var delegate: MultiPartDownloadTaskDelegate?
/// the current progress, from 0 to 1
var progress: CGFloat {
var total: Int64 = 0
var written: Int64 = 0
parts.forEach({ part in
total += part.size
written += part.bytesWritten
})
guard total > 0 else { return 0 }
return CGFloat(written) / CGFloat(total)
}

fileprivate var parts = [DownloadTask]()
fileprivate var contentLength: Int64?
fileprivate let url: URL
private var session: URLSession
private var isStoped = false
private var isResumed = false
/// When the download started
private var startedAt: Date
/// An estimate on how long left before the download is over
var remainingTimeEstimate: CGFloat {
let progress = self.progress
guard progress > 0 else { return CGFloat.greatestFiniteMagnitude }
return CGFloat(Date().timeIntervalSince(startedAt)) / progress * (1 - progress)
}

fileprivate init(from url: URL, in session: URLSession) {
self.url = url
self.session = session
startedAt = Date()

getRemoteResourceSize().then { [weak self] size -> Void in
guard let wself = self else { return }
wself.contentLength = size
wself.createDownloadParts()

if wself.isResumed {
wself.resume()
}
}.catch { [weak self] error in
guard let wself = self else { return }
wself.isStoped = true
}
}

/// Start the download
func resume() {
guard !isStoped else { return }
startedAt = Date()
isResumed = true
parts.forEach({ $0.request.resume() })
}

/// Cancels the download
func cancel() {
guard !isStoped else { return }
parts.forEach({ $0.request.cancel() })
}

/// Fetch the file size of a remote resource
private func getRemoteResourceSize(completion: @escaping (Int64?, Error?) -> Void) {
var headRequest = URLRequest(url: url)
headRequest.httpMethod = "HEAD"
session.dataTask(with: headRequest, completionHandler: { (data, response, error) in
if let error = error {
completion(nil, error)
return
}
guard let expectedContentLength = response?.expectedContentLength else {
completion(nil, FileCacheError.sizeNotAvailableForRemoteResource)
return
}
completion(expectedContentLength, nil)
}).resume()
}

/// Split the download request into multiple request to use more bandwidth
private func createDownloadParts() {
guard let size = contentLength else { return }

let numberOfRequests = 20
for i in 0..<numberOfRequests {
let start = Int64(ceil(CGFloat(Int64(i) * size) / CGFloat(numberOfRequests)))
let end = Int64(ceil(CGFloat(Int64(i + 1) * size) / CGFloat(numberOfRequests)))
parts.append(DownloadTask(for: url, from: start, to: end, in: session))
}
}

fileprivate func didFail(_ error: Error) {
cancel()
delegate?.didFail(self, error: error)
}

fileprivate func didFinishOnePart() {
if parts.filter({ $0.didWriteTo != nil }).count == parts.count {
mergeFiles()
}
}

/// Put together the download files
private func mergeFiles() {
let ext = self.url.pathExtension
let destination = Constants.tempDirectory
.appendingPathComponent("\(String.random(ofLength: 5))")
.appendingPathExtension(ext)

do {
let partLocations = parts.flatMap({ $0.didWriteTo })
try FileManager.default.merge(files: partLocations, to: destination)
delegate?.didFinish(self, didFinishDownloadingTo: destination)
for partLocation in partLocations {
do {
try FileManager.default.removeItem(at: partLocation)
} catch {
report(error)
}
}
} catch {
delegate?.didFail(self, error: error)
}
}

deinit {
FileDownloadManager.shared.tasks = FileDownloadManager.shared.tasks.filter({
$0.value !== self
})
}
}

protocol MultiPartDownloadTaskDelegate: class {
/// Called when the download progress changed
func didProgress(
_ downloadTask: MultiPartsDownloadTask
)

/// Called when the download finished succesfully
func didFinish(
_ downloadTask: MultiPartsDownloadTask,
didFinishDownloadingTo location: URL
)

/// Called when the download failed
func didFail(_ downloadTask: MultiPartsDownloadTask, error: Error)
}

/// Manage files downloads
class FileDownloadManager: NSObject {
static let shared = FileDownloadManager()
private var session: URLSession!
fileprivate var tasks = [Weak<MultiPartsDownloadTask>]()

private override init() {
super.init()
let config = URLSessionConfiguration.default
config.httpMaximumConnectionsPerHost = 50
session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
}

/// Create a task to download a file
func download(from url: URL) -> MultiPartsDownloadTask {
let task = MultiPartsDownloadTask(from: url, in: session)
tasks.append(Weak(value: task))
return task
}

/// Returns the download task that correspond to the URL task
fileprivate func match(request: URLSessionTask) -> (MultiPartsDownloadTask, DownloadTask)? {
for wtask in tasks {
if let task = wtask.value {
for part in task.parts {
if part.request == request {
return (task, part)
}
}
}
}
return nil
}
}

extension FileDownloadManager: URLSessionDownloadDelegate {
public func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didWriteData bytesWritten: Int64,
totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64
) {
guard let x = match(request: downloadTask) else { return }
let multiPart = x.0
let part = x.1

part.bytesWritten = totalBytesWritten
multiPart.delegate?.didProgress(multiPart)
}

func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL
) {
guard let x = match(request: downloadTask) else { return }
let multiPart = x.0
let part = x.1

let ext = multiPart.url.pathExtension
let destination = Constants.tempDirectory
.appendingPathComponent("\(String.random(ofLength: 5))")
.appendingPathExtension(ext)

do {
try FileManager.default.moveItem(at: location, to: destination)
} catch {
multiPart.didFail(error)
return
}

part.didWriteTo = destination
multiPart.didFinishOnePart()
}

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
guard let error = error, let multipart = match(request: task)?.0 else { return }
multipart.didFail(error)
}
}

extension FileManager {
/// Merge the files into one (without deleting the files)
func merge(files: [URL], to destination: URL, chunkSize: Int = 1000000) throws {
FileManager.default.createFile(atPath: destination.path, contents: nil, attributes: nil)
let writer = try FileHandle(forWritingTo: destination)
try files.forEach({ partLocation in
let reader = try FileHandle(forReadingFrom: partLocation)
var data = reader.readData(ofLength: chunkSize)
while data.count > 0 {
writer.write(data)
data = reader.readData(ofLength: chunkSize)
}
reader.closeFile()
})
writer.closeFile()
}
}

关于ios - URLSession 下载任务比互联网连接慢得多,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41547488/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com