2017-01-09 14 views
5

मैं अग्रभूमि में फ़ाइल डाउनलोड करने के लिए URLSession और डाउनलोड टास्क का उपयोग कर रहा हूं। डाउनलोड अपेक्षा से धीमा है। मुझे मिली अन्य पोस्ट पृष्ठभूमि कार्यों के लिए समस्या का समाधान करते हैं।URL सत्र डाउनलोड इंटरनेट कनेक्शन से बहुत धीमा है

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/एस

=== संपादित की एक कनेक्शन की रिपोर्ट

मुझे लगता है कि कोडिंग पुष्टि कर सकते हैं एक मल्टीपार्ट डाउनलोड (जो दर्द का थोड़ा सा है) चीजों को बहुत तेज़ करता है।

+0

, नेटवर्क उपयोग ~ 800kb/s तक चला जाता है, जो मुझे – Guig

+0

की आवश्यकता के माध्यम से आधा रास्ता है, क्या आप अपना नमूना यूआरएल प्रदान कर सकते हैं? – shallowThought

+0

निश्चित रूप से। मैं वीडियो डाउनलोड कर रहा हूँ। एक उदाहरण हो सकता है: https://s3.amazonaws.com/mettavr/videos/patrice.sabran.z7uxwprzpvn7ltw2k/m6aXS2hqLG84HCsEg/original/IGuYW.r2zpzCh2ze.mp4 (8.5 एमबी) – Guig

उत्तर

2

यदि यह किसी की सहायता करता है, तो डाउनलोड को तेज़ करने के लिए मेरा कोड यहां है। यह फाइल डाउनलोड को कई फाइल पार्ट्स डाउनलोड में विभाजित करता है, जो उपलब्ध बैंडविड्थ का अधिक कुशलतापूर्वक उपयोग करता है। यह अभी भी गलत लगता है कि क्या करना है करने के लिए ...

अंतिम उपयोग की तरह है:

// 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() 
    } 
} 
संबंधित मुद्दे