在UIPageViewController的UIViewController中没有调用Deinitializer

时间:2017-06-17 18:08:14

标签: ios swift uiviewcontroller uipageviewcontroller

我希望我的UIViewController(它是UIPageViewController的一部分)中的以下deinitializer删除我的playerLayer并将player设置为nil以便内存为没有重载(因为当UIViewController不再需要时,应始终调用deinit):

deinit {

    self.player = nil
    self.playerLayer.removeFromSuperlayer()
    print("deinit")

}

要检查deinit是否曾被执行过,我添加了打印件,发现它永远不会被调用。有人能解释为什么不叫它?你有什么建议我去做我想做的事情?

编辑:

在Rob建议的instructions in this question之后(在评论中),我发现以下函数导致内存泄漏。如果可以在文档目录中找到文件,则该函数应该设置播放器。

setupPlayer()函数:

//setup video player
func setupPlayer() {

    //get name of file on server //self.video is a String containing the URL for a video on a server
    let fileName = URL(string: self.video!)!.lastPathComponent

    let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
    let url = NSURL(fileURLWithPath: path)
    let filePath = url.appendingPathComponent(fileName)?.path
    let fileManager = FileManager.default

    if fileManager.fileExists(atPath: filePath!) {

        //create file with name on server if not there already
        let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
        if let docDir = paths.first
        {

            let appFile  = docDir.appending("/" + fileName)
            let videoFileUrl = URL(fileURLWithPath: appFile)

            //player's video
            if self.player == nil {

                let playerItemToBePlayed = AVPlayerItem(url: videoFileUrl) //AVPlayerItem(url: videoFileUrl)

                self.player = AVPlayer(playerItem: playerItemToBePlayed)

                //add sub-layer
                playerLayer = AVPlayerLayer(player: self.player)
                playerLayer.frame = self.view.frame
                self.controlsContainerView.layer.insertSublayer(playerLayer, at: 0)

                //when are frames actually rendered (when is video loaded)
                self.player?.addObserver(self, forKeyPath: "currentItem.loadedTimeRanges", options: .new, context:nil)

                //loop through video
                NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player?.currentItem, queue: nil, using: { (_) in
                    DispatchQueue.main.async {
                        self.player?.seek(to: kCMTimeZero)
                        self.player?.play()
                    }
                })

            }

        }

    }

}

pageViewController函数(viewcontrollerAfter)

func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? 
{

    let currentIndexString  = (viewController as! MyViewController).index
    let currentIndex        = indec.index(of: currentIndexString!)

    //set if so that next page
    if currentIndex! < indec.count - 1 {

        //template
        let myViewController = MyViewController()

        //enter data into template
        myViewController.index            = self.indec[currentIndex! + 1]

        //return template with data
        return myViewController

    }

    return nil

}

编辑2:

如您所见,没有追溯,请注意此malloc(右上角)和类似大型mallocs(左下角)的大小。

no traceback

1 个答案:

答案 0 :(得分:2)

如果我们在&#34; Debug Memory Graph&#34;中查看对象图,我们可以看到:

enter image description here

我们可以看到视图控制器被闭包(中间路径)捕获。我们还可以看到观察者正在保持强烈的参考(底部路径)。

因为我打开了&#34; Malloc stack&#34;功能(如https://stackoverflow.com/a/30993476/1271826所示),我可以点击&#34;关闭捕获&#34;并且可以在右侧面板中看到堆栈跟踪:

enter image description here

(请原谅我那个内存图与第一个屏幕快照略有不同,因为我修复了另一个内存问题,即观察者,正如本答案末尾所讨论的那样。)

无论如何,如果我点击堆栈跟踪中黑色的最高条目(即该堆栈跟踪中我自己代码的最后一位),它会直接将我们带到违规代码:

enter image description here

这引起我们对原始代码的注意:

NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player?.currentItem, queue: nil, using: { (_) in
    DispatchQueue.main.async {
        self.player?.seek(to: kCMTimeZero)
        self.player?.play()
    }
})

关闭是对self的强烈引用。您可以通过以下方式纠正错误:

NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: .main) { [weak self] _ in
    self?.player?.seek(to: kCMTimeZero)
    self?.player?.play()
}

注意,闭包中的[weak self]捕获列表。

顺便说一句,虽然您不需要在nilplayer deinit,但您需要删除观察员。我还为您的观察者设置了context,以便observerValue(forKeyPath:of:change:context:)能够知道它是否需要处理。

这可能导致类似:

private var observerContext = 0
private weak var observer: NSObjectProtocol?

func setupPlayer() {
    let fileName = URL(string: video!)!.lastPathComponent

    let fileManager = FileManager.default
    let videoFileUrl = try! fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
        .appendingPathComponent(fileName)

    if fileManager.fileExists(atPath: videoFileUrl.path), player == nil {
        let playerItemToBePlayed = AVPlayerItem(url: videoFileUrl)

        player = AVPlayer(playerItem: playerItemToBePlayed)

        //add sub-layer
        playerLayer = AVPlayerLayer(player: player)
        playerLayer.frame = view.bounds
        controlsContainerView.layer.insertSublayer(playerLayer, at: 0)

        //when are frames actually rendered (when is video loaded)
        player?.addObserver(self, forKeyPath: "currentItem.loadedTimeRanges", options: .new, context: &observerContext)

        //loop through video
        observer = NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: .main) { [weak self] _ in
            self?.player?.seek(to: kCMTimeZero)
            self?.player?.play()
        }
    }
}

deinit {
    print("deinit")

    // remove loadedTimeRanges observer 

    player?.removeObserver(self, forKeyPath: "currentItem.loadedTimeRanges")

    // remove AVPlayerItemDidPlayToEndTime observer

    if let observer = observer {
        NotificationCenter.default.removeObserver(observer)
    }
}

// note, `observeValue` should check to see if this is something 
// this registered for or whether it should pass it along to `super`

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    guard context == &observerContext else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        return
    }

    // do something
}