带有SearchController的TableView – DEINIT不被调用

我已经添加了一个search栏和search显示控制器从界面生成器到我的应用程序。 我无法正确地取消(dealloc)。

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

  • 用户不search任何东西,只要从tableView中select一个项目, 调用DEINIT
  • 用户search的东西(或只是在search栏中点击),取消search,从tableView中select项目, 调用DEINIT
  • 用户search的东西(或只是点击search栏),并从tableView中select一个项目, DEINIT不被称为 🙁

如果我在导航控制器中select“返回”而不是select一个项目,则会发生同样的情况。

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

任何帮助感激!

更新进一步的testing显示,从视图控制器中删除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仍然没有答案我find了。 真的希望有人能帮助!

UPDATE 5为了响应@ConfusedByCode – 我更新了以下方法, [unowned self] in所有闭包或后台线程操作中使用[unowned self] in

 code removed - refer to COMPLETE CODE at bottom of post 

我还没有看到DEINIT。 我正在检查,以确保我没有在某个地方犯了一个愚蠢的错误。

更新6我已经删除了额外的弱自我,并确保封闭正在利用[weak self] in和解开他们的安全。 DEINIT仍然没有被调用。

更新7更改了两件事无果 – 使appDel unowned let appDel ,并把searchSear.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所build议的那样,definePresentationContext中存在某种保留错误,最初是在我的viewWillAppear方法中。 我删除了这一行,并做了一些轻微的按摩我的代码。 它现在正在完美地工作。

非常感谢你的努力和答案! 另外,感谢@confusedByCode的帮助 – 我确信他的build议也是我的问题的一个组成部分,但最终的结果并不是最终的答案。

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

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

去掉:

 definesPresentationContext = true // Remove this line... 

在这里阅读:

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


UISearchController中必须有一个bug。 一些苹果代码必须保留你的控制器。

也许转换视图(animation)没有完成,当控制器是不连接的(它有特殊的toView和fromView等,我们不能达到),这可以防止控制器被释放。

我认为你应该把这个文件作为苹果的一个bug。


另外我会build议改变你的deinit到:

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

这将确保search控制器从不尝试呈现东西时,取消分配,因为这会造成警告和潜在的意外行为。

我今天遇到了这个问题,以及这行代码似乎工作,让你的课堂释放

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

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

我认为问题在于你在闭包和自我之间创造了很强的参照周期,因为你在闭包中使用了一个未包装的弱自我。

例如,这个:

 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 self或者unowned self ,而不是在closures和解包之外,你的对象将被正确地初始化。