无法从 URLSessionTaskDelegate 获取数据

Posted

技术标签:

【中文标题】无法从 URLSessionTaskDelegate 获取数据【英文标题】:Cannot get data from URLSessionTaskDelegate 【发布时间】:2017-09-24 20:05:44 【问题描述】:

我正在尝试向我的应用程序添加后台获取功能。目前网络调用完成后会调用委托函数urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL),但是缓存目录中的URL不存在:

class DownloadManager: NSObject, URLSessionTaskDelegate, URLSessionDownloadDelegate 

static var shared = DownloadManager()

var session : URLSession 
    get 
        let config = URLSessionConfiguration.background(withIdentifier: "my_Identifier")
        return URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue())
    


func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) 
    print(location.absoluteString)
    do 
        let myData = try Data(contentsOf: location)
     catch let error 
        print(error.localizedDescription)
      // The The file “file_id.tmp” couldn’t be opened because there is no such file.
    


public func fetch() 
    guard let url = URL(string: "#myURL") else 
        return
    

    let task = session.downloadTask(with: url)
    task.resume()
  

在我的 App Delegate 中:

func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) 
    print("Executing background fetch")
    DownloadManager.shared.fetch()
    completionHandler(.newData)

我错过了什么?

【问题讨论】:

您的代码在我尝试时似乎可以正常工作。您的 URL 是否可能以 http 而不是 https 开头?这可能会使其与 App Transit Security 发生冲突。 他们都是https。你是如何模拟这个后台抓取的? 我刚刚将您的课程复制到 CodeRunner 中,将 URL 替换为我网站上的文件,并在底部添加了一行以调用 DownloadManager.shared.fetch()。它似乎下载正确。 @AndrewWalz 你能发一个小项目来证明这个问题吗? @AndrewWalz 你检查我的答案了吗?它对你有用吗? 【参考方案1】:

试试这个:

import UIKit

class ViewController: UIViewController 

    @IBOutlet weak var progressView: UIProgressView!

    override func viewDidLoad() 
        let _ = DownloadManager.shared.activate()
    

    override func viewWillAppear(_ animated: Bool) 
        super.viewWillAppear(animated)
        DownloadManager.shared.onProgress =  (progress) in
            OperationQueue.main.addOperation 
                self.progressView.progress = progress //assign progress value
            
        
    

    override func viewWillDisappear(_ animated: Bool) 
        super.viewWillDisappear(animated)
        DownloadManager.shared.onProgress = nil
    

    @IBAction func startDownload(_ sender: Any) 
        let url = URL(string: "YourFileURL")!
        DownloadManager.shared.download(url)
    


替换你的DownloadManager:

    import Foundation

class DownloadManager : NSObject, URLSessionDelegate, URLSessionDownloadDelegate 

    static var shared = DownloadManager()
    var url : URL?
    typealias ProgressHandler = (Float) -> ()

    var onProgress : ProgressHandler? 
        didSet 
            if onProgress != nil 
                let _ = activate()
            
        
    

    override private init() 
        super.init()
    

    func activate() -> URLSession 
        let config = URLSessionConfiguration.background(withIdentifier: "\(Bundle.main.bundleIdentifier!).background")

        // Warning: If an URLSession still exists from a previous download, it doesn't create a new URLSession object but returns the existing one with the old delegate object attached!
        return URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue())
    

    private func calculateProgress(session : URLSession, completionHandler : @escaping (Float) -> ()) 
        session.getTasksWithCompletionHandler  (tasks, uploads, downloads) in
            let progress = downloads.map( (task) -> Float in
                if task.countOfBytesExpectedToReceive > 0 
                    return Float(task.countOfBytesReceived) / Float(task.countOfBytesExpectedToReceive)
                 else 
                    return 0.0
                
            )
            completionHandler(progress.reduce(0.0, +))
        
    

    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) 

        if totalBytesExpectedToWrite > 0 
            if let onProgress = onProgress 
                calculateProgress(session: session, completionHandler: onProgress)
            
            let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
            debugPrint("Progress \(downloadTask) \(progress)")

        
    

    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) 
        debugPrint("Download finished: \(location)")
//        try? FileManager.default.removeItem(at: location)
        //copy downloaded data to your documents directory with same names as source file
        let documentsUrl =  FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
        let destinationUrl = documentsUrl!.appendingPathComponent(url!.lastPathComponent)
        let dataFromURL = try? Data(contentsOf: location)
        try? dataFromURL?.write(to: destinationUrl, options: [.atomic])
        print(destinationUrl)
        //now it is time to do what is needed to be done after the download
    

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) 
        debugPrint("Task completed: \(task), error: \(String(describing: error))")
    


    func download(_ url: URL)
    
        self.url = url

        //download identifier can be customized. I used the "ulr.absoluteString"
        let task = DownloadManager.shared.activate().downloadTask(with: url)
        task.resume()

    

在我的 App Delegate 中:

func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) 
        debugPrint("handleEventsForBackgroundURLSession: \(identifier)")
        completionHandler()
    

参考:Tutorial by ralfebert

【讨论】:

以上是关于无法从 URLSessionTaskDelegate 获取数据的主要内容,如果未能解决你的问题,请参考以下文章

无法从资源加载位图

为啥我无法从 Alamofire 获取请求结果

DateTimeParseException:无法解析文本:无法从 TemporalAccessor 获取 LocalDateTime

无法从 'char[]' 转换为 'string[]' [关闭]

无法远程调试应用程序 - 端口无法从外部访问

无法从状态 '' 解析 '...'