Master AgentX
Master AgentX

Reputation: 395

Swift: UIViewController instance not receiving updates from URLSession Delegate methods

I'm unable to update UIViewController from URLSession Delegate methods.

I have a TableView containing different users and a detail view controller which contains several images associated with that user.

Now that I need to download images, I create a background URL session with a unique identifier for each user. I'm doing this because I have to use the same Detail UIViewController for each user from TableView.

> DetailViewController

// Download service for this controller. Shared instance because it tracks active downloads. /// [URL:Download]

let downloadService:DownloadService = MediaDownloadManager.shared.downloadService


// A Unique background download session for each user

lazy var downloadSession: URLSession = {
    let identifer:String = "com.example.app" + "-user-" + userID   /// userID passed from parent
    let configuration = URLSessionConfiguration.background(withIdentifier: identifer)
    return URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}()


override func viewDidLoad() {
    super.viewDidLoad()
    downloadService.downloadsSession = downloadSession

    /// I get this every time when I exit and re-enter the controller which is obvious.
    /// [DEBUG CONSOLE] A background URLSession with identifier com.example.app-user-12 already exists!
}

And I've conformed URLSession Delegates to my DetailViewController in order to track download progress and update views.

extension DetailViewController: URLSessionDelegate, URLSessionDownloadDelegate {
    func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { /// }
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { /// }
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { /// }
}

Now the problem is, when I run the app and open the DetailViewController for a user and start the download process for images I need, It works fine and I get updates from urlsession delegate methods which then updates my tableView.

But when I swipe back to TableViewController and then reopen the DetailViewController for that same user, and start downloading images, the DetailViewController's tableView does not receive update from those urlsession delegate methods.

I found what's exactly causing this problem. The URLSession Delegate methods capture the instance of my view controller. Which means when I update my DetailViewController's TableView, from the URLSession Delegate methods, they actually try to update the tableView of the first and former UIViewController instance.

Below is the code for updating cell progress.

func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
    guard let downloadURL = downloadTask.originalRequest?.url else {
        printAndLog(message: "### \(#function) Failed to retrieve original request download url", log: .network, logType: .error)
        return
    }
    let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
    let downloadedSize = ByteCountFormatter.string(fromByteCount: totalBytesWritten, countStyle: .file)
    let totalSize = ByteCountFormatter.string(fromByteCount: totalBytesExpectedToWrite, countStyle: .file)

    print(self) /// Same instance everytime.

    if let download = downloadService.activeDownloads[downloadURL] {
        download.progress = progress
        DispatchQueue.main.async {
            if let index = self.images.firstIndex(of: download.image),
                let cell = self.tableView.cellForRow(at: IndexPath(row: index, section: 0)) as? ImageCell {
                cell.updateProgress(progress: download.progress, loadedSize: downloadedSize, totalSize: totalSize)
            }
        }
    }
}

How can I solve this annoying problem? I know singletons are a good answer but I want to avoid singletons as much possible.

I can't use downloadSession.finishTasksAndInvalidate() as I think it will not continue my downloads when I exit the controller.

My Requirements:

1-) Download should continue when I exit the controller.

2-) Downloading should be possible for each instance of DetailViewController, i.e, for each user

3-) Background downloads are a must of course.

Pardon if you think the title of this question is wrong.

Upvotes: 0

Views: 140

Answers (1)

JoelFan
JoelFan

Reputation: 38714

You need to add another level of abstraction. The views should not directly subscribe to the background download process. Some other class should subscribe to that and the views should be updated from that other class.

That way you only have that once class subscribing, even if multiple views need the data.

Upvotes: 1

Related Questions