I have a basic viewController
with a single button that when tapped, calls a method to start downloading an image from a given valid url.
I was very careful of passing any strong pointers to the viewcontroller. And I am able to dismiss the viewcontroller away and go back to the presenting controller without any issues. However, objects instances of Web()
that are created by the viewcontroller, are not deallocated, even if the viewcontroller's deinit
is called.
What am I doing wrong?
BTW: the file download is started, progress is reported and file location is reported as well. However, the object is never deallocated once the file is finished downloading.
viewController.swift
@IBAction func buttonTapped(_ sender: UIButton) {
//first create an instance of Web class (download helper)
let web = Web(url: "https://www.google.com.sa/images/branding/googlelogo/2x/googlelogo_color_120x44dp.png")
// all call back closures have no reference to self
web.downloadFile(
finishedHandler: {fileLocation in print("file stored to \(fileLocation)")},
progressHandler: {bytes,total in print("written \(bytes) out of \(total)")},
errorHandler: {msg in print("error: \(msg)")}
)
}
And I have defined Web()
as NSObject
which conforms to URLSessionDownloadDelegate
to take advantage of delegate event methods (didFinishDownload
/ didWriteBytes
, etc).
Web.swift
import UIKit
class Web : NSObject {
var urlToDownload : String?
// the following variables are references to closures passed to the object.
var progressCallback : ((_ bytesWritten:Int64, _ totalExpectedBytes: Int64)->())?
var finishedCallback : ((_ fileLocation: String)->())?
static var instanceCount = 0 // keep track of number of instances created
init(url: String) {
Web.instanceCount += 1
urlToDownload = url
print(" new instance of Web created. Total : \(Web.instanceCount)")
}
deinit {
Web.instanceCount -= 1
print("Web instance deallocated. Remaining: \(Web.instanceCount)")
}
}
and I added the file download logic as extension to the same file:
extension Web : URLSessionDownloadDelegate {
func downloadFile(
finishedHandler: @escaping (_ fileLocation:String)->(),
progressHandler: @escaping (_ bytesWritten:Int64, _ totalBytes: Int64)->(),
errorHandler: @escaping (_ errorMsg:String)->()) {
// we need to capture the closure because, these will
// be called once the delegate methods are triggered
self.progressCallback = progressHandler
self.finishedCallback = finishedHandler
if let url = URL(string: self.urlToDownload!) {
let session = URLSession(
configuration: .default,
delegate: self,
delegateQueue: nil)
let task = session.downloadTask(with: url)
task.resume()
}
}
// MARK :- Delegate methods
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// call the closure if it still exists
self.finishedCallback?(location.absoluteString)
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
// report progress by calling captured closure, if exists
self.progressCallback?(totalBytesWritten,totalBytesExpectedToWrite)
}
}