2017-01-09 20 views
5

Tôi đang sử dụng URLSession và downloadTask để tải xuống tệp ở nền trước. Tải xuống chậm hơn nhiều so với dự kiến. Các bài viết khác tôi tìm thấy giải quyết vấn đề cho các nhiệm vụ nền.Tải xuống URLSessionTask chậm hơn nhiều so với kết nối Internet

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) 

Tôi đang quan sát một cách sử dụng mạng của ~ 150kb/s trong khi một thử nghiệm tốc độ trên điện thoại của tôi báo cáo một kết nối là 5MB/s

=== Sửa

Tôi có thể xác nhận rằng mã hóa tải xuống nhiều phần (một chút đau đớn để làm) tăng tốc mọi thứ bằng rất nhiều.

+0

Nếu tôi bắt đầu một số lượng tải cùng một lúc, việc sử dụng mạng tăng lên ~ 800kb/s, một nửa là thông qua những gì tôi cần – Guig

+0

Bạn có thể vui lòng cung cấp URL mẫu của mình không? – shallowThought

+0

Chắc chắn. Tôi đang tải video xuống. Một ví dụ có thể là: https://s3.amazonaws.com/mettavr/videos/patrice.sabran.z7uxwprzpvn7ltw2k/m6aXS2hqLG84HCsEg/original/IGuYW.r2zpzCh2ze.mp4 (8,5 Mb) – Guig

Trả lời

2

Nếu điều đó giúp mọi người, đây là mã của tôi để tăng tốc độ tải xuống. Nó chia nhỏ tệp tải xuống trong một số tệp tải xuống phần, sử dụng băng thông có sẵn hiệu quả hơn. Nó vẫn cảm thấy sai lầm khi phải làm điều đó ...

Việc sử dụng cuối cùng là như:

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

và đây là đoạn code:

/// 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() 
    } 
} 
Các vấn đề liên quan