我已将搜索栏和搜索显示控制器从界面生成器添加到我的应用程序。我无法让它正确地 deinit (dealloc)。
它显示以下行为(swift2,ios9):
如果我在导航控制器中选择“返回”而不是选择某个项目,则会发生相同的行为。
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 所建议的,defineesPresentationContext 出现了某种保留错误,该错误最初位于我的 viewWillAppear 方法中。我已经删除了该行并对我的代码做了一些轻微的修改。现在可以完美运行了。
非常感谢您的努力和回答!另外,感谢@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
}
}
}
我今天也遇到这个问题了 这行代码似乎可以让你的课程发布
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 = true // Remove this line...
在这里阅读:
UISearchController 一定有 bug。某些 Apple 代码必须保留您的控制器。
当控制器取消链接时,可能过渡视图(动画)尚未完成(它有特殊的 toView 和 fromView 等,我们无法到达),这会阻止控制器被释放。
我认为你应该将此作为 bug 提交给 Apple。
我还建议将你的 deinit 更改为:
deinit {
print("TBVC Dealloc")
if let superView = searchController.view.superview
{
superView.removeFromSuperview()
}
}
这将确保搜索控制器在释放时永远不会尝试呈现内容,因为这会产生警告和潜在的意外行为。
我认为问题在于你在闭包和 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 self
或 unowned self
,而不是在闭包外部并展开它,您的对象将被正确地取消初始化。
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if searchController.isActive {
searchController.isActive = false
}
}