如何在后台使用 session.uploadTask 我崩溃了

Mad*_*ddy 3 ios swift

我要崩溃了

它说***由于未捕获的异常“NSGenericException”而终止应用程序,原因:“后台会话中不支持完成处理程序块。改用委托。

var Boundary = "\(boundary.generateBoundaryString())_boundary"
private lazy var session: URLSession = {
    let config = URLSessionConfiguration.background(withIdentifier: "MyUniqeId")
    config.isDiscretionary = true
    config.sessionSendsLaunchEvents = true
    return URLSession(configuration: config, delegate: self, delegateQueue: nil)
}()

func webServiceForUploadImages(urlStr:String,params:[String:String],fileUrl:String,imageData:Data,success :@escaping (AppMedia) -> Void ,failure:@escaping (NSError) -> Void) -> Void{
    let url = Constant.BASE_URL + urlStr
    print(url)
    if(reachAbility.connection != .none){
        var request = URLRequest(url: URL(string: url)!)
        request.httpMethod = "POST"
        request.allHTTPHeaderFields = Header.headers()
        request.setValue("multipart/form-data; boundary=\(Boundary)", forHTTPHeaderField: "Content-Type")
        let data = try! createBody(with: params, filePathKey: "file", paths: [fileUrl], boundary: "\(Boundary)", imageData: imageData)

        session.uploadTask(with: request, from: data) { (data, response, err) in
            if response != nil{
                guard let response = response as? HTTPURLResponse else {return}
                handleError.shared.HandleReponseTokenExpireError(dataResponse: response, success: { (response) in
                })
                if(err != nil){
                    print("\(err!.localizedDescription)")
                }
                guard let responseData = data else {
                    print("no response data")
                    return
                }
                if let responseString = String(data: responseData, encoding: .utf8) {
                    DispatchQueue.main.async {
                        let dict = Utility.jsonToDict(str: responseString)
                        let mediaDict = AppMedia(fromDictionary: dict as! [String : Any])
                        Constant.KAppDelegate.hideProgressHUD()
                        success(mediaDict)
                    }

                   // print("uploaded to: \(responseString)")
                }
            }else{
                DispatchQueue.main.async {
                    failure(err! as NSError)
                    Constant.KAppDelegate.hideProgressHUD()
                    Constant.KAppDelegate.showErrorMessage(title: "Error", message: Constant.ServerError, msgType: .error)
                }
            }

            }.resume()
    }else{
         self.showErrorMsg(str: Constant.ConnectivityError)
    }
}
Run Code Online (Sandbox Code Playgroud)

让 config = URLSessionConfiguration.background(withIdentifier: "MyUniqeId") 使用这个让我崩溃

Rob*_*Rob 7

要使用背景上传URLSessionConfiguration,有一些特殊注意事项:

  1. 不能使用完成处理程序(因为当您完成上传时应用程序可能没有运行)。您必须使用基于委托的方法,例如uploadTask(with:fromFile:).

    例如:

     func startUpload(for request: URLRequest, from data: Data) throws -> URLSessionUploadTask {
         let fileURL = URL(fileURLWithPath: NSTemporaryDirectory())
             .appendingPathComponent(UUID().uuidString)
         try data.write(to: fileURL)
         let task = session.uploadTask(with: request, fromFile: fileURL)
         task.resume()
    
         return task
     }
    
    Run Code Online (Sandbox Code Playgroud)

    这显然假设您已经指定delegate并实现了适当的委托方法:

     extension BackgroundSession: URLSessionDelegate {
         func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
             DispatchQueue.main.async {
                 self.savedCompletionHandler?()
                 self.savedCompletionHandler = nil
             }
         }
     }
    
     extension BackgroundSession: URLSessionTaskDelegate {
         func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
             if let error = error {
                 print(error)
                 return
             }
             print("success")
         }
     }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 请注意,我们不能使用,uploadTask(with:from:)因为它Data用于第二个参数,这在后台会话中是不允许的。相反,必须将请求的正文保存到文件中,然后使用uploadTask(with:fromFile:).

  3. 请记住处理在您的应用程序未运行时上传完成的场景。即,应用程序委托handleEventsForBackgroundURLSession必须捕获完成处理程序。例如,我将有一个属性BackgroundSession来保存完成处理程序:

     func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) { 
         BackgroundSession.shared.savedCompletionHandler = completionHandler
     }
    
    Run Code Online (Sandbox Code Playgroud)

    然后你想实现urlSessionDidFinishEvents(forBackgroundURLSession:)并调用保存的完成处理程序:

     extension BackgroundSession: URLSessionDelegate {
         func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
             DispatchQueue.main.async {
                 self.savedCompletionHandler?()
                 self.savedCompletionHandler = nil
             }
         }
     }
    
    Run Code Online (Sandbox Code Playgroud)

顺便说一下,在后台下载文件讨论了许多这些注意事项(例如委托 API 而不是基于闭包的 API、应用程序委托问题等)。它甚至讨论了上传任务也是基于文件的要求。


无论如何,这是一个示例BackgroundSession管理器:

import os.log

// Note, below I will use `os_log` to log messages because when testing background URLSession
// you do not want to be attached to a debugger (because doing so changes the lifecycle of an
// app). So, I will use `os_log` rather than `print` debugging statements because I can then
// see these logging statements in my macOS `Console` without using Xcode at all. I'll log these
// messages using this `OSLog` so that I can easily filter the macOS `Console` for just these
// logging statements.

private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: #file)

class BackgroundSession: NSObject {
    var savedCompletionHandler: (() -> Void)?
    
    static var shared = BackgroundSession()
    
    private var session: URLSession!
    
    private override init() {
        super.init()

        let identifier = Bundle.main.bundleIdentifier! + ".backgroundSession"
        let configuration = URLSessionConfiguration.background(withIdentifier: identifier)

        session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
    }
}

extension BackgroundSession {
    @discardableResult
    func startUpload(for request: URLRequest, from data: Data) throws -> URLSessionUploadTask {
        os_log("%{public}@: start", log: log, type: .debug, #function)

        let fileURL = URL(fileURLWithPath: NSTemporaryDirectory())
            .appendingPathComponent(UUID().uuidString)
        try data.write(to: fileURL)
        let task = session.uploadTask(with: request, fromFile: fileURL)
        task.resume()

        return task
    }
}

extension BackgroundSession: URLSessionDelegate {
    func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
        os_log(#function, log: log, type: .debug)

        DispatchQueue.main.async {
            self.savedCompletionHandler?()
            self.savedCompletionHandler = nil
        }
    }
}

extension BackgroundSession: URLSessionTaskDelegate {
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if let error = error {
            os_log("%{public}@: %{public}@", log: log, type: .error, #function, error.localizedDescription)
            return
        }
        os_log("%{public}@: SUCCESS", log: log, type: .debug, #function)
    }
}

extension BackgroundSession: URLSessionDataDelegate {
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didBecome downloadTask: URLSessionDownloadTask) {
        os_log(#function, log: log, type: .debug)
    }
    
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        os_log("%{public}@: received %d", log: log, type: .debug, #function, data.count)
    }
}
Run Code Online (Sandbox Code Playgroud)

而且,当然,我的应用程序委托:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    ...

    func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
        BackgroundSession.shared.savedCompletionHandler = completionHandler
    }
}
Run Code Online (Sandbox Code Playgroud)

毋庸置疑,上传与 background 一起需要担心很多URLSession。如果您通过慢速连接上传大量资产(例如视频),也许您需要这样做。但另一种(更简单的)替代方法是使用默认URLSession配置,只需告诉操作系统即使用户离开您的应用程序,也请求多一点时间来完成上传。只需将标准完成处理程序模式与 default 结合使用URLSession,并将其与Extending Your App's Background Execution Time 中概述的技术结合使用。现在,这只会为您带来 30 秒左右的时间(过去在较旧的 iOS 版本中为 3 分钟),但通常这就是我们所需要的。但是,如果您认为完成上传可能需要 30 秒以上,那么您将需要 background URLSessionConfiguration