2017-01-09 14 views
5

Ön planda bir dosya indirmek için URLSession ve downloadTask kullanıyorum. İndirme beklenenden çok daha yavaş. Bulduğum diğer yayınlar, arka plan görevleri için sorunu ele alıyor. Cihazımda bir hız testi 5MB/sURLSession downloadTask internet bağlantısından daha yavaştır

=== Edit bağlantı bildiriyor ise

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) 

Ben ~ 150kb/s olan bir ağ kullanımını gözlemliyorum

O kodlamayı teyit edebilir Çok parçalı bir indirme (ki bu biraz da olsa bir acıdır) bir çok şeyi hızlandırır.

+0

:

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

ve burada kod: Hala

nihai kullanım gibi ... bunu yapmak zorunda yanlış geliyor ağ kullanımı – Guig

+0

ihtiyacım olanın yarısı olan ~ 800kb/s'ye kadar çıkıyor, lütfen örnek URL'nizi sağlayabilir misiniz? – shallowThought

+0

Elbette. Videoları indiriyorum. Bir örnek şudur: https://s3.amazonaws.com/mettavr/videos/patrice.sabran.z7uxwprzpvn7ltw2k/m6aXS2hqLG84HCsEg/original/IGuYW.r2zpzCh2ze.mp4 (8.5 Mb) – Guig

cevap

2

Bu, herhangi birine yardımcı olursa, indirme işlemini hızlandıran kodum. Dosya yüklemesini, mevcut bant genişliğini daha verimli kullanan bir dizi dosya parçası yüklemesine böler. Ben seferde indirme bir dizi başlarsanız

/// 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() 
    } 
} 
İlgili konular