因未捕获的异常'NSRangeException'而终止应用程序,原因是:'无法删除观察者 - ios

Ams*_*eer 6 ios swift xcode7

我正在使用swift开发ios app.我使用的是xcode7.0.1.使用TableViewController.当我点击行时我想展开,当我再次点击它时折叠.我正在关注gitHub的教程.现在我正面临着这个错误Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer for the key path "frame" from <X.expandTips 0x145d8d20> because it is not registered as an observer.'

我希望下面的代码行会导致问题.

我的UITableViewCell类代码:

func checkHeight(){
        expandaple_view.hidden = (frame.size.height < expandTips.expandedHeight)
    }

    func WatchFrameChanges() {
        addObserver(self , forKeyPath: "frame", options: .New, context: nil )
        checkHeight() 
    }

    func ignoreFrameChanges(){

        removeObserver(self, forKeyPath: "frame")

    }

    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        if keyPath == "frame"{
            checkHeight()
        }
    }
Run Code Online (Sandbox Code Playgroud)

在我的TableViewController代码中:

override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
       (cell as! expandTips) . WatchFrameChanges()
    }

    override func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
        (cell as! expandTips) . ignoreFrameChanges()
    }

    override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        if (indexPath == selectedIndexPath ){
            return expandTips.expandedHeight
        }else {
            return expandTips.defaultHeight
        }
    }
Run Code Online (Sandbox Code Playgroud)

我是ios的新手.我希望这一定是个简单的问题.请有人帮我解决这个问题.

我不知道有什么细节需要在这里发布.如果我想添加更多细节,请发表评论.

Ams*_*eer 13

从@Leandros的评论我找到了解决方案.只需使用布尔变量跟踪观察者.

使用以下代码,我找到了解决方案:

 var frameAdded = false
 func checkHeight(){

        expanding_view.hidden = (frame.size.height < expandTips.expandedHeight)
    }

    func WatchFrameChanges() {
        if(!frameAdded){
        addObserver(self , forKeyPath: "frame", options: .New, context: nil )
            frameAdded = true
        }
    }

    func ignoreFrameChanges() {
          if(frameAdded){
        removeObserver(self, forKeyPath: "frame")
            frameAdded = false
        }
    }

    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        if keyPath == "frame"{
            checkHeight()
        }
    }
    deinit {
        print("deinit called");
        ignoreFrameChanges()
    }
Run Code Online (Sandbox Code Playgroud)


Lea*_*ros 7

如果没有注册观察者,则删除KVO观察者会抛出异常.

不幸的是,没有办法检查观察者是否已注册.众所周知的解决方法是捕获这样的异常(Objective-C):

@try {
    [object removeObserver:self forKeyPath:NSStringFromSelector(@selector(isFinished))];
} @catch (NSException * __unused exception) {}
Run Code Online (Sandbox Code Playgroud)

要在Swift中执行它,你必须使用Swift 2.1,因为它缺乏支持try {} catch {}.你可以看到它在2.1如何工作在这里.

更正:尽管Swift 2引入了自己的错误处理约定,但是使用do/try/catch关键字,这些并不意味着它们与Objective-C中的相同,并且仍然存在(从Swift 2.1和Xcode 7.1开始)Swift中无法处理NSExceptions除了将Objective-C代码写入@catch它们之外,从系统库抛出,然后从Swift调用该代码.

你可以在这里阅读更多关于KVO的内容,其中还有一个关于安全取消订阅的部分,其中提到了这种不幸的设计.

  • 看起来,Swift仍然无法捕获NSExceptions,就像Objective-C一样.你现在有两种方法可以解决你的问题,一种是丑陋的,一种是好的.丑陋的方式是在Objective-C中编写代码部分,并从Swift中调用它,好的方法是,如果已经删除了观察者,则跟踪它,如果已经删除它,则不是简单地调用它. (2认同)