4
votes

I have background downloading zip file:

if let url = NSURL(string: urlstring) 
         {            
            let config = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier((NSUUID().UUIDString))
            let session = NSURLSession(configuration: config, delegate: self, delegateQueue: nil)
            let task = session.downloadTaskWithURL(url)
            session.sessionDescription = filepath
            if let sessionId = session.configuration.identifier
            {
                print("start zip session: " + sessionId)
            }

            task.resume()               
            }
        }

it works cool if you have internet connection but if you lose it during downloading app just wait and URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) will not be called never. How it can be handle?
Something like time for response from server

3

3 Answers

4
votes

The question is old, but still unanswered, so here is one workaround.

Some clarifications first

In general there is a property of URLSessionConfiguration called waitsForConnectivity which can be set to false where URLSessionTask will then directly fail on connection lost. However if true then URLSessionTaskDelegate will receive a callback on the urlSession(_:taskIsWaitingForConnectivity:) method.

However

Background tasks such as DownloadTask always wait for connectivity and ignore waitsForConnectivity property of URLSessionConfiguration. They also DON'T trigger urlSession(_:taskIsWaitingForConnectivity:) callback, so there is no official way to listen for connectivity drop-out on download task.

Workaround

If you listening for the download progress you'll notice that a call to the method is done few times in a second. Therefore we can conclude that if the progress callback is not called for more than 5 seconds, then there could be a connectivity drop issue. So the workaround is to make additional property to the URLSessionDownloadDelegate delegate and store the last update of the progress. Then have interval function to periodically check whether this property were not updated soon.

Something like:

    class Downloader: NSObject, URLSessionTaskDelegate, URLSessionDownloadDelegate {

        var lastUpdate: Date;
        var downloadTask: URLSessionDownloadTask?;

        public var session : URLSession {
            get {
                let config = URLSessionConfiguration.background(
                withIdentifier: "\(Bundle.main.bundleIdentifier!).downloader");
                config.isDiscretionary = true;

                return URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue());
            }
        }

        override init() {
            self.lastUpdate = Date();
            super.init();
        }

        func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
            // Handle download completition
            // ...
        }

        func urlSession(
            _ session: URLSession,
            downloadTask: URLSessionDownloadTask,
            didWriteData bytesWritten: Int64,
            totalBytesWritten writ: Int64,
            totalBytesExpectedToWrite exp: Int64)
        {
            let progress = 100 * writ / exp;
            // Do something with the progress
            // ...

            self.lastUpdate = Date();
        }
    }

    var downloader = Downloader();
    let url = "http://...";
    var request = URLRequest(url: URL(string: url)!);
    currentWorker.downloadTask = downloader.session.downloadTask(with: request);
    currentWorker.downloadTask!.resume();

    // Schedule timer for every 5 secs
    var timer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: "CheckInternetDrop", userInfo: nil, repeats: true);

func CheckInternetDrop(){
    let interval = Date().timeIntervalSinceDate(downloader.lastUpdate);
    if (interval > 5) {
        print("connection dropped");
    }
}

Very simple example...

1
votes

According to apple docs on pausing and resuming downloads :

Also, downloads that use a background configuration will handle resumption automatically, so manual resuming is only needed for non-background downloads.

Besides, the reason waitsForConnectivity is being ignored is stated in the downloading files in the background :

If your app is in the background, the system may suspend your app while the download is performed in another process.

This is why even if you were to build a timer in a similar fashion to Dimitar Atanasov it will not work once the app goes in the background since it will be suspended.

I've tested the background downloading myself with network failures and the system resumes without fail, hence no extra code is required.

0
votes

You can set timeouts for your requests:

config.timeoutIntervalForRequest = <desired value>
config.timeoutIntervalForResource = <desired value>

Documentation: