5

I have an app where user can download multiple files, sequentially. I have followed Mr. Rob's solution for the sequential downloads. However, I have problem when I try to cancel the download.

There are two situation when I try to cancel the download.

  1. I want to cancel the current downloading file. When I cancel that file, the download can proceed to the next file in the queue
  2. I want to cancel the file that are currently in the queue. The queue has cancelAll() method which will cancel all file in the queue.

Here is the codes

DownloadManager.swift

class DownloadManager: NSObject, NSURLSessionTaskDelegate, NSURLSessionDownloadDelegate
{

    /// Dictionary of operations, keyed by the `taskIdentifier` of the `NSURLSessionTask`
    internal var delegate : DownloadVC!
    private var operations = [Int: DownloadOperation]()

    /// Serial NSOperationQueue for downloads

    let queue: NSOperationQueue = {
        let _queue = NSOperationQueue()
        _queue.name = "download"
        _queue.maxConcurrentOperationCount = 1
        return _queue
    }()

    /// Delegate-based NSURLSession for DownloadManager

    lazy var session: NSURLSession = {
        let sessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
        return NSURLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: nil)
    }()

    /// Add download
    ///
    /// - parameter URL:  The URL of the file to be downloaded
    ///
    /// - returns: The DownloadOperation of the operation that was queued

    func addDownload(URL: NSURL) -> DownloadOperation
    {
        print("url in download manager: \(URL)")
        let operation = DownloadOperation(session: session, URL: URL)
        operations[operation.task.taskIdentifier] = operation
        queue.addOperation(operation)
        return operation
    }

    /// Cancel all queued operations

    func cancelAll()
    {
        queue.cancelAllOperations()

    }

//    func cancelOne()
//    {
//        operations[identifier]?.cancel()
//        print("identifier : \(identifier)")
//        //queue.operations[identifier].cancel()
//       // cancelAll()
//    }

    // MARK: NSURLSessionDownloadDelegate methods

    func URLSession(session: NSURLSession,
        downloadTask: NSURLSessionDownloadTask,
        didFinishDownloadingToURL location: NSURL)
    {
        print("downloadTask.taskIdentifier \(downloadTask.taskIdentifier)")
        operations[downloadTask.taskIdentifier]?.delegate = delegate
        operations[downloadTask.taskIdentifier]?.URLSession(session, downloadTask: downloadTask, didFinishDownloadingToURL: location)
    }

    func URLSession(session: NSURLSession,
        downloadTask: NSURLSessionDownloadTask,
        didWriteData bytesWritten: Int64,
        totalBytesWritten: Int64,
        totalBytesExpectedToWrite: Int64)
    {
        operations[downloadTask.taskIdentifier]?.delegate = delegate
        operations[downloadTask.taskIdentifier]?.URLSession(session, downloadTask: downloadTask, didWriteData: bytesWritten, totalBytesWritten: totalBytesWritten, totalBytesExpectedToWrite: totalBytesExpectedToWrite)
    }

    // MARK: NSURLSessionTaskDelegate methods

    func URLSession(session: NSURLSession,
        task: NSURLSessionTask,
        didCompleteWithError error: NSError?)
    {
            let key = task.taskIdentifier
            operations[key]?.URLSession(session, task: task, didCompleteWithError: error)
            operations.removeValueForKey(key)
    }
}

DownloadOperation.swift

class DownloadOperation : AsynchronousOperation
{
    let task: NSURLSessionTask
    var percentageWritten:Float = 0.0
    var delegate : DataDelegate!

    init(session: NSURLSession, URL: NSURL)
    {
        task = session.downloadTaskWithURL(URL)
        super.init()
    }

    override func cancel() {
        task.cancel()
        super.cancel()
    }

    override func main() {
        task.resume()

    }

    // MARK: NSURLSessionDownloadDelegate methods

        func URLSession(session: NSURLSession,
            downloadTask: NSURLSessionDownloadTask,
            didWriteData bytesWritten: Int64,
            totalBytesWritten write: Int64,
            totalBytesExpectedToWrite expect: Int64)
        {
            //download bar progress
            percentageWritten = Float(write) / Float(expect)
            //progressBar.progress = percentageWritten
            let pW = Int(percentageWritten*100)
            delegate.didWriteData(pW)
        }

        // using cocoa Security for encryption and decryption
        func URLSession(session: NSURLSession,
            downloadTask: NSURLSessionDownloadTask,
            didFinishDownloadingToURL location: NSURL)
        {
            let documentsDirectoryURL =  NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first as NSURL?
            print("Finished downloading!")
            print(documentsDirectoryURL)
            let downloadLocation = "\(location)".stringByReplacingOccurrencesOfString("file://", withString: "")

            let fileData = NSFileManager().contentsAtPath(downloadLocation)

            delegate.didFinishDownloadingToUrl(fileData!)
        }


    // MARK: NSURLSessionTaskDelegate methods

    func URLSession(session: NSURLSession,
        task: NSURLSessionTask,
        didCompleteWithError error: NSError?)
    {
        completeOperation()
        if error != nil {
            print(error)
        }
    }
}

AsynchronousOperation.swift

class AsynchronousOperation : NSOperation
{

    override var asynchronous: Bool { return true }

    private var _executing: Bool = false
    override var executing: Bool
        {
        get
        {
            return _executing
        }
        set {
            if (_executing != newValue)
            {
                self.willChangeValueForKey("isExecuting")
                _executing = newValue
                self.didChangeValueForKey("isExecuting")
            }
        }
    }

    private var _finished: Bool = false
    override var finished: Bool
        {
        get
        {
            return _finished
        }
        set
        {
            if (_finished != newValue)
            {
                self.willChangeValueForKey("isFinished")
                _finished = newValue
                self.didChangeValueForKey("isFinished")
            }
        }
    }

    func completeOperation()
    {
        if executing {
            executing = false
            finished = true
        }
    }

    override func start()
    {
        if (cancelled) {
            finished = true
            executing = false
            return
        }

        executing = true

        main()
    }

}

DownloadViewController.swift

This is where I want to cancel the download.

 if cell.downloadLabel.currentTitle == "CANCEL"
        {
            print("cancel button was pressed")

            downloadManager.cancelAll()

// I want to put the codes here

            let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
            let key = cell.stringId
            print("key : \(key)")
            defaults.setBool(false, forKey: key)
            defaults.synchronize()

            cell.accessoryType = UITableViewCellAccessoryType.None
            cell.downloadLabel.backgroundColor = UIColor.redColor()
            cell.downloadLabel.setTitle("DOWNLOAD", forState: .Normal)
        }

For situation 1, I have tried to use do

queue.operations[identifier].cancel()

but it crash saying

*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'

For situation 2, I have tried to put all downloaded urls in an array, add into queue, once the cancel button is clicked, it will cancelAll() queue, remove the cancel one from queue and re-insert the remaining urls back into the array. However, it won't work

Can anybody help me to satisfy both situation?

Community
  • 1
  • 1
Syafiq Mastor
  • 178
  • 2
  • 10
  • 1
    Possible duplicate of [How to stop current NSOperation?](http://stackoverflow.com/questions/7820960/how-to-stop-current-nsoperation) – sunshinejr Nov 26 '15 at 08:00

1 Answers1

3

You need to keep a reference to the NSOperation instance. Then you can call cancel on it.

orkoden
  • 18,946
  • 4
  • 59
  • 50