【问题标题】:TableView with SearchController - DEINIT not called带有 SearchController 的 TableView - 未调用 DEINIT
【发布时间】:2015-11-02 21:48:23
【问题描述】:

我已从界面构建器向我的应用程序添加了搜索栏和搜索显示控制器。我无法让它正确地 deinit (dealloc)。

它显示以下行为(swift2,ios9):

  • 用户不搜索任何东西,只是从 tableView 中选择一个项目,调用 DEINIT
  • 用户搜索某些内容(或只是点击搜索栏),取消搜索,从 tableView 中选择项目,调用 DEINIT
  • 用户搜索某些内容(或只是点击搜索栏),然后从 tableView 中选择一个项目,不调用 DEINIT :(

如果我在导航控制器中选择“返回”而不是选择一个项目,则会发生相同的行为。

code removed - refer to COMPLETE CODE at bottom of post.

任何帮助表示赞赏!

更新 进一步的测试表明,从视图控制器中完全删除 progressHud/loadingHud 对不调用 DEINIT 没有影响。一定是和tableview或者searchcontroller本身有关……

更新 2 我尝试在 viewWillDissapear 中调用 searchBarCancelButtonClicked() 方法,但它仍然没有释放。即使您单击“取消”然后导航离开它也会...

更新 3 将 willDisappear/didDisappear 更改为以下内容对 DEINIT 没有影响 - 但不会出现错误的界面问题(感谢 Polina)。我正在尽一切努力获得释放,但到目前为止还没有运气。

    override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(true)
    searchBarCancelButtonClicked(searchController.searchBar)
}

override func viewDidDisappear(animated: Bool) {
    print("View did disappear")
    searchController.searchBar.resignFirstResponder()
    searchController.searchBar.endEditing(true)
    searchController.active = false
    loadingHud.removeFromSuperview()
    progressHud.removeFromSuperview()
    searchController.searchBar.delegate = nil
    searchController.searchResultsUpdater = nil
    searchController = nil
    tableView = nil

    super.viewDidDisappear(true)

}

更新 4 我仍然没有找到答案。真心希望有人能帮忙!

更新 5 作为对 @ConfusedByCode 的回应 - 我已更新以下方法以在所有闭包或后台线程操作中使用 [unowned self] in

code removed - refer to COMPLETE CODE at bottom of post

我仍然没有看到 DEINIT。我正在检查以确保我没有在某个地方犯下愚蠢的错误。

更新 6 我已经删除了额外的弱自我,并确保闭包正在使用 [weak self] in 并安全地打开它们。 DEINIT 仍未被调用。

更新 7 更改了两件事无济于事 - 制作 appDel unowned let appDel,并将 searchBar.resignFirstResponder() 放入 finishSearch()。仍然没有收到 deinit。

完整代码:(代表更新 7)

如需正确答案,请参阅粘贴在正确代码下的代码

class AirportSearchTBVC: UITableViewController, UISearchResultsUpdating, UISearchBarDelegate {
var airportData = [Dictionary<String, String>]()
var filteredData = [Dictionary<String, String>]()
var searchController: UISearchController!
unowned let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
var progressHud: ProgressHUD!
var loadingHud: ProgressHUD!
var arrDepOfFlight: String!
var dateOfFlight: NSDate!
var tailNum: String!
var selectedAirportIdent: String!

deinit {
    print("TBVC Dealloc")

}
override func viewDidLoad() {
    super.viewDidLoad()
}

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(true)

    progressHud = ProgressHUD(text: "Searching")
    loadingHud = ProgressHUD(text: "Loading")
    searchController = UISearchController(searchResultsController: nil)
    searchController.searchResultsUpdater = self
    searchController.dimsBackgroundDuringPresentation = false
    searchController.searchBar.sizeToFit()
    tableView.tableHeaderView = searchController.searchBar
    definesPresentationContext = true
    searchController.hidesNavigationBarDuringPresentation = false
    searchController.searchBar.delegate = self
    view.addSubview(loadingHud)

    appDel.backgroundThread(background: { [weak self] in
        if let weakSelf = self {
            let airportHelper = AirportHelper()
            weakSelf.airportData = airportHelper.getAirportSearchData()
        }
        },
        completion: {
            dispatch_async(dispatch_get_main_queue()) { [weak self] in
                if let weakSelf = self {
                    if weakSelf.isVisible && weakSelf.isTopViewController {
                        weakSelf.filteredData = (weakSelf.airportData)
                        weakSelf.loadingHud.removeFromSuperview()
                        weakSelf.updateSearchResultsForSearchController(weakSelf.searchController)
                        weakSelf.tableView.reloadData()

                    }

                }
            }
    });

}

//MARK: Searchbar methods (All background thread methods are in here)
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
    searchController.searchBar.endEditing(true)
    searchController.searchBar.resignFirstResponder()
    filteredData = airportData
    tableView.reloadData()
}
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
    if isVisible && isTopViewController {
        if let startCount = searchController.searchBar.text?.length {
            if searchController.searchBar.text!.length >= 3 && searchController.searchBar.text!.length == startCount{
                view.addSubview(progressHud)
                finishSearch()
            }
        }
    }
}
func finishSearch () {
    appDel.backgroundThread(background: { [weak self] in
        if let weakSelf = self {
            if weakSelf.isVisible && weakSelf.isTopViewController {
                let searchText = weakSelf.searchController.searchBar.text!.lowercaseString

                weakSelf.searchController.searchBar.resignFirstResponder()
                weakSelf.filteredData = weakSelf.airportData.filter{
                    if let ident = $0["ident"] {
                        if ident.lowercaseString.rangeOfString(searchText) != nil {
                            return true
                        }
                    }
                    if let name  = $0["name"] {
                        if name.lowercaseString.rangeOfString(searchText) != nil {
                            return true
                        }
                    }
                    if let city = $0["municipality"] {
                        if city.lowercaseString.rangeOfString(searchText) != nil {
                            return true
                        }
                    }
                    return false
                }
            }
        }
        },
        completion: {
            dispatch_async(dispatch_get_main_queue()) { [weak self] in

                if let weakSelf = self {
                    if weakSelf.isVisible && weakSelf.isTopViewController {
                        weakSelf.tableView.reloadData()
                        weakSelf.progressHud.removeFromSuperview()
                    }
                }
            }
    });
}

func updateSearchResultsForSearchController(searchController: UISearchController) {
    if isVisible && isTopViewController {
        if let startCount = searchController.searchBar.text?.length {
            if searchController.searchBar.text!.length >= 3 && searchController.searchBar.text!.length == startCount{
                view.addSubview(progressHud)
                finishSearch()
            }
        }
    }
}


//MARK: Table view methods:
override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return 72
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    // #warning Incomplete implementation, return the number of sections
    return 1
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // #warning Incomplete implementation, return the number of rows



    if searchController.active {
        return filteredData.count
    } else {
        return airportData.count
    }


}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    var cell: AirportSearchTableViewCell
    cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! AirportSearchTableViewCell
    if searchController.active {
        let airportDict = filteredData[indexPath.row]
        let airportIdent = airportDict["ident"]
        let airportName = airportDict["name"]
        let airportRegion = airportDict["iso_region"]
        let airportCity = airportDict["municipality"]
        cell.loadItem(airportIdent, name: airportName, region: airportRegion, city: airportCity)
    } else {
        let airportDict = airportData[indexPath.row]
        let airportIdent = airportDict["ident"]
        let airportName = airportDict["name"]
        let airportRegion = airportDict["iso_region"]
        let airportCity = airportDict["municipality"]
        cell.loadItem(airportIdent, name: airportName, region: airportRegion, city: airportCity)
    }
    return cell
}
override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(true)
    searchBarCancelButtonClicked(searchController.searchBar)
}

override func viewDidDisappear(animated: Bool) {
    print("View did disappear")
    searchController.searchBar.resignFirstResponder()
    searchController.searchBar.endEditing(true)
    searchController.active = false
    searchController.delegate = nil
    searchController.resignFirstResponder()
    loadingHud.removeFromSuperview()
    progressHud.removeFromSuperview()
    searchController.searchBar.delegate = nil
    searchController.searchResultsUpdater = nil
    searchController.removeFromParentViewController()
    searchController = nil
    tableView = nil
    super.viewDidDisappear(true)

}

func delay(delay:Double, closure:()->()) {
    dispatch_after(
        dispatch_time(
            DISPATCH_TIME_NOW,
            Int64(delay * Double(NSEC_PER_SEC))
        ),
        dispatch_get_main_queue(), closure)
}



// MARK: - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    let cell = tableView.cellForRowAtIndexPath(indexPath) as! AirportSearchTableViewCell
    selectedAirportIdent = cell.identLbl.text!
    self.performSegueWithIdentifier("searchMapVC", sender: nil)
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    // Get the new view controller using segue.destinationViewController.
    // Pass the selected object to the new view controller.
    if segue.identifier == "searchMapVC" {
        let mapVC = segue.destinationViewController as! SearchMapController
        mapVC.arrDepOfFlight = arrDepOfFlight
        mapVC.dateOfFlight = dateOfFlight
        mapVC.tailNum = tailNum
        mapVC.selectedAirportIdent = selectedAirportIdent
    }
}
}
//MARK: EXTENSIONS
extension String {
    var length: Int { return characters.count    }  // Swift 2.0
}
extension UIViewController {
    public var isVisible: Bool {
        if isViewLoaded() {
            return view.window != nil
        }
        return false
}

public var isTopViewController: Bool {
        if self.navigationController != nil {
            return self.navigationController?.visibleViewController === self
        } else if self.tabBarController != nil {
            return self.tabBarController?.selectedViewController == self && self.presentedViewController == nil
        } else {
            return self.presentedViewController == nil && self.isVisible
        }
    }

}

更正的代码 [已修复] 正如 Mikael Hellman 所建议的那样,最初在我的 viewWillAppear 方法中的 definePresentationContext 发生了某种保留错误。我已经删除了该行并对我的代码进行了一些轻微的按摩。它现在运行良好。

非常感谢您的努力和回答!另外,感谢@confusedByCode 的帮助 - 我确信他的建议也是我的问题的一部分,但最终并没有成为最终答案。

import UIKit

class AirportSearchTBVC: UITableViewController, UISearchResultsUpdating, UISearchBarDelegate {
var airportData = [Dictionary<String, String>]()
var filteredData = [Dictionary<String, String>]()
var searchController: UISearchController!
let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
var progressHud: ProgressHUD!
var loadingHud: ProgressHUD!
var arrDepOfFlight: String!
var dateOfFlight: NSDate!
var tailNum: String!
var selectedAirportIdent: String!

deinit {
    print("TBVC Dealloc")

}
override func viewDidLoad() {
    super.viewDidLoad()
}

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(true)

    progressHud = ProgressHUD(text: "Searching")
    loadingHud = ProgressHUD(text: "Loading")
    searchController = UISearchController(searchResultsController: nil)
    searchController.searchResultsUpdater = self
    searchController.dimsBackgroundDuringPresentation = false
    searchController.searchBar.sizeToFit()
    tableView.tableHeaderView = searchController.searchBar
    //definesPresentationContext = true
    searchController.hidesNavigationBarDuringPresentation = false
    searchController.searchBar.delegate = self
    view.addSubview(loadingHud)

    appDel.backgroundThread(background: { [weak self] in
        if let weakSelf = self {
            let airportHelper = AirportHelper()
            weakSelf.airportData = airportHelper.getAirportSearchData()
        }
        },
        completion: {
            dispatch_async(dispatch_get_main_queue()) { [weak self] in
                if let weakSelf = self {
                    print("isVisible: \(weakSelf.isVisible)")
                    print("isTopViewController: \(weakSelf.isTopViewController)")
                    if weakSelf.isVisible  {
                        weakSelf.filteredData = (weakSelf.airportData)
                        weakSelf.loadingHud.removeFromSuperview()
                        weakSelf.updateSearchResultsForSearchController(weakSelf.searchController)
                        weakSelf.tableView.reloadData()
                    }


                }
            }
    });

}

//MARK: Searchbar methods (All background thread methods are in here)
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
    searchController.searchBar.endEditing(true)
    searchController.searchBar.resignFirstResponder()
    filteredData = airportData
    tableView.reloadData()
}
func searchBarSearchButtonClicked(searchBar: UISearchBar) {

        if let startCount = searchController.searchBar.text?.length {
            if searchController.searchBar.text!.length >= 3 && searchController.searchBar.text!.length == startCount{
                view.addSubview(progressHud)
                finishSearch()
            }
        }

}
func finishSearch () {
    appDel.backgroundThread(background: { [weak self] in
        if let weakSelf = self {

                let searchText = weakSelf.searchController.searchBar.text!.lowercaseString

                //weakSelf.searchController.searchBar.resignFirstResponder()
                weakSelf.filteredData = weakSelf.airportData.filter{
                    if let ident = $0["ident"] {
                        if ident.lowercaseString.rangeOfString(searchText) != nil {
                            return true
                        }
                    }
                    if let name  = $0["name"] {
                        if name.lowercaseString.rangeOfString(searchText) != nil {
                            return true
                        }
                    }
                    if let city = $0["municipality"] {
                        if city.lowercaseString.rangeOfString(searchText) != nil {
                            return true
                        }
                    }
                    return false
                }

        }
        },
        completion: {
            dispatch_async(dispatch_get_main_queue()) { [unowned self] in

                if self.isVisible {

                        self.tableView.reloadData()
                        self.progressHud.removeFromSuperview()
                }



            }
    });
}

func updateSearchResultsForSearchController(searchController: UISearchController) {

        if let startCount = searchController.searchBar.text?.length {
            if searchController.searchBar.text!.length >= 3 && searchController.searchBar.text!.length == startCount{
                view.addSubview(progressHud)
                finishSearch()
            }
        }

}


//MARK: Table view methods:
override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return 72
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    // #warning Incomplete implementation, return the number of sections
    return 1
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // #warning Incomplete implementation, return the number of rows



    if searchController.active {
        return filteredData.count
    } else {
        return airportData.count
    }


}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    var cell: AirportSearchTableViewCell
    cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! AirportSearchTableViewCell
    if searchController.active {
        let airportDict = filteredData[indexPath.row]
        let airportIdent = airportDict["ident"]
        let airportName = airportDict["name"]
        let airportRegion = airportDict["iso_region"]
        let airportCity = airportDict["municipality"]
        cell.loadItem(airportIdent, name: airportName, region: airportRegion, city: airportCity)
    } else {
        let airportDict = airportData[indexPath.row]
        let airportIdent = airportDict["ident"]
        let airportName = airportDict["name"]
        let airportRegion = airportDict["iso_region"]
        let airportCity = airportDict["municipality"]
        cell.loadItem(airportIdent, name: airportName, region: airportRegion, city: airportCity)
    }
    return cell
}
override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(true)
    searchController.active = false
    loadingHud.removeFromSuperview()
    progressHud.removeFromSuperview()
}

override func viewDidDisappear(animated: Bool) {
    print("View did disappear")


    super.viewDidDisappear(true)

}

func delay(delay:Double, closure:()->()) {
    dispatch_after(
        dispatch_time(
            DISPATCH_TIME_NOW,
            Int64(delay * Double(NSEC_PER_SEC))
        ),
        dispatch_get_main_queue(), closure)
}



// MARK: - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    let cell = tableView.cellForRowAtIndexPath(indexPath) as! AirportSearchTableViewCell
    selectedAirportIdent = cell.identLbl.text!
    self.performSegueWithIdentifier("searchMapVC", sender: nil)
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    // Get the new view controller using segue.destinationViewController.
    // Pass the selected object to the new view controller.
    if segue.identifier == "searchMapVC" {
        let mapVC = segue.destinationViewController as! SearchMapController
        mapVC.arrDepOfFlight = arrDepOfFlight
        mapVC.dateOfFlight = dateOfFlight
        mapVC.tailNum = tailNum
        mapVC.selectedAirportIdent = selectedAirportIdent
    }
}
}
//MARK: EXTENSIONS
extension String {
    var length: Int { return characters.count    }  // Swift 2.0
}
extension UIViewController {
    public var isVisible: Bool {
        if isViewLoaded() {
            return view.window != nil
        }
        return false
}

public var isTopViewController: Bool {
        if self.navigationController != nil {
            return self.navigationController?.visibleViewController === self
        } else if self.tabBarController != nil {
            return self.tabBarController?.selectedViewController == self && self.presentedViewController == nil
        } else {
            return self.presentedViewController == nil && self.isVisible
        }
    }

}

【问题讨论】:

  • 我已经指出了错误,但我还没有接近解决方案...当您集中搜索时,deinit 不会启动。我认为这是因为有一些苹果的魔法会自动触发动画(按照超级视图,你会发现苹果拥有的 UITransitionView)。我相信正是这种转变保留了控制器……但我没有办法消除它,或者阻止这种转变的发生……:(
  • 至少你已经接近了!我当然不是 - 我觉得我已经尝试了所有方法,但它变得非常令人沮丧:(

标签: ios swift uitableview retain uisearchcontroller


【解决方案1】:

我今天也遇到了这个问题 这行代码似乎可以让您的课程发布

  override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
    searchController?.dismissViewControllerAnimated(false, completion: nil)
  }

这是我在 Dropbox 上的示例项目 https://www.dropbox.com/s/zzs0m4n9maxd2u5/TestSearch.zip?dl=0

【讨论】:

  • 不再需要将definesPresentationContext 设置为false。这是正确的处理方式。
【解决方案2】:

删除了我的旧答案,发现了问题。

删除:

definesPresentationContext = true // Remove this line...

在这里阅读:

https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/#//apple_ref/occ/instp/UIViewController/definesPresentationContext


UISearchController 中一定有 bug。某些 Apple 代码必须保留您的控制器。

可能当控制器取消链接时过渡视图(动画)没有完成(它有特殊的 toView 和 fromView 等,我们无法访问),这会阻止控制器被释放。

我认为您应该将此作为错误提交给 Apple。


我还建议将您的 deinit 更改为:

deinit {
  print("TBVC Dealloc")
  if let superView = searchController.view.superview
  {
    superView.removeFromSuperview()
  }
}

这将确保搜索控制器在被释放时永远不会尝试呈现内容,因为这会产生警告和潜在的意外行为。

【讨论】:

  • 这真的很令人兴奋 - 我会尽快尝试并报告!
  • 你,先生,是男人。一百万年后我永远不会发现这一点。非常感谢您的努力,请享受赏金!
  • @Charlie 谢谢!很高兴能帮上忙。
  • 有时需要将definesPresentationContext 设置为true,具体取决于搜索栏的上下文。例如,如果您的 UITableView 被推送到 UINavigationController 上,那么通常需要将 definesPresentationContext 设置为 true。否则,搜索栏会从导航控制器呈现,并在推送和弹出控制器时引起探测。因此,虽然这似乎可以解决问题,但它并不能处理所有用例。
  • @chris 是对的。对于任何被卡住的人,除了不将definePresentationContext设置为true之外,您还希望在viewWillDisappear中隐藏您的searchBar并使其重新出现在viewWillAppear中。这不会将 searchBar 添加到其他视图控制器。
【解决方案3】:

我认为问题在于您在闭包和 self 之间创建了强引用循环,因为您在闭包中使用了未包装的弱 self。

例如,这个:

if let safeSelf = weakSelf {
    appDel.backgroundThread(background: {
            let airportHelper = AirportHelper()
            safeSelf.airportData = airportHelper.getAirportSearchData()
        },
        completion: {
            dispatch_async(dispatch_get_main_queue()) {
                if safeSelf.isVisible && safeSelf.isTopViewController {
                    safeSelf.filteredData = (safeSelf.airportData)
                    safeSelf.loadingHud.removeFromSuperview()
                    safeSelf.tableView.reloadData()
                }
            }
    });
}

应该是:

appDel.backgroundThread(background: { [weak self] in
        let airportHelper = AirportHelper()
        self?.airportData = airportHelper.getAirportSearchData()
    },
    completion: {
        dispatch_async(dispatch_get_main_queue()) { [weak self] in
            if self?.isVisible && safeSelf.isTopViewController {
                self?.filteredData = (safeSelf.airportData)
                self?.loadingHud.removeFromSuperview()
                self?.tableView.reloadData()
            }
        }
});

您也可以使用[unowned self],您不必将它们视为可选,但老实说,我忘记了无主与弱的优缺点。但我相信,如果您在闭包的捕获列表中声明weak selfunowned self,而不是在闭包之外并展开它,您的对象将被正确地取消初始化。

【讨论】:

  • 我现在正在尝试这个 - 与 [unowned self] 一起尝试避免一些令人困惑的未包装错误,我担心这些错误可能会导致保留。感谢您的帖子将报告发生的情况!
  • 我已将更新 5 添加到我的问题中,您能否查看更新并指出我做错了什么?我已尝试以否定结果应用您的答案。
  • 我刚刚注意到,当您将 searchController 和 tableView 设置为 nil 时,您可以取消初始化 vc。这可能是因为您正在为 weak self 创建一个局部变量并将其设置为 resultsUpdater 和 searchController 委托。文档说 searchControllers 自动将 searchResultsUpdater 设置为弱,所以你不需要自己做。尝试摆脱你正在制作的所有额外的weak selfs,并设置 searchResultsUpdater 并直接委托给 self。你只需要在闭包的捕获列表中使用弱自我。我认为他们可能弊大于利。
  • 我相信我已经删除了所有令人反感的弱自我演员。请参阅更新 6 - 仍未解除分配。我试图尽可能地简化使用后台线程的方法以使其更容易。我真的比你想象的更感谢你的帮助!
  • 哇,恐怕我没有主意了。我注意到的最后一件事是您可以尝试在searchBarSearchButtonClickedupdateSearchResultsForSeachController 中调用searchBar.resignFirstReponder()。如果这不起作用,您可能需要在 finishSearch 方法的闭包之一中调用 resignFirstResponder,因为它是异步执行的。除此之外,我没有想法对不起!
猜你喜欢
  • 2023-03-31
  • 1970-01-01
  • 2015-01-14
  • 2021-10-22
  • 2018-05-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-21
相关资源
最近更新 更多