如何解决Swift 4中的内存循环URLSession.downloadTask?

Ahm*_*mad 4 ios automatic-ref-counting swift

我有一个基本viewController的单个按钮,当点击时,调用一个方法开始从给定的有效网址下载图像.

我非常小心地将任何强大的指针传递给viewcontroller.而且我可以将视图控制器解散掉并且回到演示控制器而没有任何问题.但是,Web()即使deinit调用了viewcontroller,viewController创建的对象实例也不会被释放.

我究竟做错了什么?

BTW:启动文件下载,报告进度并报告文件位置.但是,一旦文件下载完毕,该对象永远不会被释放.

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)")}
  )

}
Run Code Online (Sandbox Code Playgroud)

我已经定义Web()NSObject哪些符合URLSessionDownloadDelegate利用委托事件方法(didFinishDownload/ didWriteBytes等).

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)")

   }
}
Run Code Online (Sandbox Code Playgroud)

我将文件下载逻辑添加为同一文件的扩展名:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

Ahm*_*d F 5

在我跟踪了这​​个问题后(使用Memory Graph Debugger),我在这段代码(Web类 - 下载文件方法)中找出问题的原因:

if let url = URL(string: self.urlToDownload!) {
     let session = URLSession(
        configuration: .default,
        delegate: self,
        delegateQueue: nil)

    let task = session.downloadTask(with: url)

    task.resume()
}
Run Code Online (Sandbox Code Playgroud)

实际上有一个与实例化有关的问题URLSession而没有摆脱它; 因此,你必须做一个 - 手动处理来解决它,调用finishTasksAndInvalidate()适合这样的问题:

if let url = URL(string: self.urlToDownload!) {
    let session = URLSession(
        configuration: .default,
        delegate: self,
        delegateQueue: nil)

    let task = session.downloadTask(with: url)

    task.resume()
    // here we go:
    session.finishTasksAndInvalidate()
}
Run Code Online (Sandbox Code Playgroud)

为了确保它按预期工作,我建议在委托方法和deinitWeb类中添加断点,你应该看到它按预期工作(委托方法可以工作然后deinit调用).


此外:

如果您想了解有关如何使用Memory Graph Debugger的更多信息,您可以检查:当Leaks仪器没有显示时,如何调试内存泄漏?