Search code examples
iosswiftnsurlsession

swift: URLSessionConfiguration


I have FirstViewController with button to move in SecondTableViewController. In SecondTableViewController I have cell and if I click on cell downloading starts.

Problem: If I move in SecondTableViewController from FirstViewController to start downloading and return in FirstViewController and after move in SecondTableViewController I get this:

A background URLSession with identifier com.example.DownloadTaskExample.background already exists!

And I can not download my files. How to fix it?

my code in SecondTableViewController:

var backgroundSession: URLSession!
var index = 0

override func viewDidLoad() {
    super.viewDidLoad()
    let sessionConfig = URLSessionConfiguration.background(withIdentifier: "com.example.DownloadTaskExample.background")
        backgroundSession = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: OperationQueue())
}

code for download files:

let url = URL(string: "link")!
let downloadTaskLocal = ViewController.backgroundSession.downloadTask(with: url)
downloadTaskLocal.resume()

New code:

class Networking {
    static let shared = Networking()
    var backgroundSession = URLSession(configuration: URLSessionConfiguration.background(withIdentifier: "com.example.DownloadTaskExample.background"), delegate: URLSession() as? URLSessionDelegate, delegateQueue: OperationQueue())
}

class ViewController: UITableViewController, URLSessionDownloadDelegate {

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        let url = URL(string: "link")!
        let downloadTaskLocal = Networking.shared.backgroundSession.downloadTask(with: url)
        downloadTaskLocal.resume()
    }
}

UPD

class BackgroundSession: NSObject {

    static let shared = BackgroundSession()

    static let identifier = "com.example.DownloadTaskExample.background"

    var session: URLSession!

    private override init() {
        super.init()

        let configuration = URLSessionConfiguration.background(withIdentifier: BackgroundSession.identifier)
        session = URLSession(configuration: configuration, delegate: self as? URLSessionDelegate, delegateQueue: OperationQueue())
    }


}

class ViewController: UITableViewController, URLSessionDownloadDelegate{

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let url = URL(string: "http:link\(indexPath.row + 1)")!
        let downloadTaskLocal = BackgroundSession.shared.session.downloadTask(with: url)
        downloadTaskLocal.resume()
}
}

Solution

  • If you're really using background session, you should make sure to instantiate it only once. You need to make this single URLSession instance available to not only multiple instances of the second view controller, but you also need to have your app delegate be able to reference it (e.g. handleEventsForBackgroundURLSession has to save the completion handler, to be called but session delegate's urlSessionDidFinishEvents(forBackgroundURLSession:)).

    One approach is to have the app delegate instantiate it and then pass it along. Even easier, you can use singleton pattern (as shown in https://stackoverflow.com/a/44140059/1271826).

    The trick is that in decoupling this background session from any particular instance of the second view controller, how do you want to inform this second view controller of events from your background session. You might want to use NotificationCenter. Or you could give your background session some closure properties that the second view controller could set (and reset for every new instance). It's hard to say precisely without knowing what that second view controller is doing.

    But the key is to make sure you have only one instance of the background session during the lifetime of the app.


    By the way, two other problems. Consider:

    session = URLSession(configuration: configuration, delegate: self as? URLSessionDelegate, delegateQueue: OperationQueue())
    
    1. You never conformed to URLSessionDelegate (and you hid this problem with as? conditional type casting). Remove the as? URLSessionDelegate. And obviously, also add conformance with an extension:

      extension BackgroundSession: URLSessionDelegate { ... }
      

      Obviously, inside that, implement whatever URLSessionDelegate methods you need, if any.

      You probably also want to conform to URLSessionTaskDelegate and URLSessionDownloadDelegate in their own extensions, too, if you want to implement any of those methods, too.

    2. We generally want URLSession to use a serial queue. As the documentation says:

      The queue should be a serial queue, in order to ensure the correct ordering of callbacks. If nil, the session creates a serial operation queue for performing all delegate method calls and completion handler calls.

      So, easiest, just pass nil as that third parameter, and a serial queue will be created for you. But by passing OperationQueue(), you’re supplying a concurrent queue, when you really want a serial one.

    Thus that line of code should be replaced with:

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