【发布时间】: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