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

7
我正在使用Swift开发iOS应用程序,使用的是Xcode7.0.1和TableViewController。当我点击行时,希望能够展开并在再次点击时折叠。我正在遵循gitHub上的教程。现在我面临错误Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observerfor 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()
        }
    }

而在我的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
        }
    }

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

我不知道需要在这里发布哪些详细信息。如果我需要添加更多细节,请评论。


1
我几乎可以确定这段代码来自我的YouTube视频,所以我会在这里提供链接(https://www.youtube.com/watch?v=VWgr_wNtGPM)。这个KVO注销的修复实际上已经被提交到了存储库(https://github.com/rcdilorenzo/Cell-Expander)。 - Christian Di Lorenzo
是的,我正在关注这个GitHub项目。这个问题已经被@mattiaskronberg注册了。我从这个Pastebin链接中找到了解决方案。http://pastebin.com/VaRmuYfU - Amsheer
2个回答

15

通过 @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()
    }

感谢您对解决方案的快速响应。 - felixwcf

7

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

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

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

要在Swift中实现它,你必须使用Swift 2.1,因为在那之前它不支持try {} catch {}。你可以在这里看到它在2.1中是如何工作的:here

更正:尽管Swift 2引入了自己的错误处理约定,使用do/try/catch关键字,但这并不意味着它们在Objective-C中的含义相同。截至Swift 2.1和Xcode 7.1,仍然没有办法处理从系统库抛出的NSExceptions,除非编写Objective-C代码来@catch它们,然后从Swift调用该代码。

您可以在此处了解有关KVO的更多信息:here,其中还有一个关于安全取消订阅的部分,提到了这个不幸的设计。


我像这样进行更改。func ignoreFrameChanges() throws -> Void{ do{ try removeObserver(self, forKeyPath: "frame") }catch{ print(error) } }。现在我面临两个警告:“在'try'表达式中没有调用抛出函数”和“'catch'块是不可达的,因为在'do'块中没有抛出错误”。 - Amsheer
仍然应用程序关闭 - Amsheer
2
看起来,Swift 仍然无法像 Objective-C 一样捕获 NSExceptions。现在你有两种方法来解决问题,一种丑陋的,一种好的。丑陋的方法是用 Objective-C 编写代码部分,并从 Swift 中调用它;而好的方法是跟踪是否已经移除了观察者,如果已经移除,则不要简单地不调用它。 - Leandros
我创建了一个NSObject类别,提供了一个方法safelyRemoveObserver:forKeyPath:https://gist.github.com/samuel-mellert/2bb38ba4887aeecac462ad972e25d26f您必须从Swift中调用它,以便捕捉当前未注册观察者时抛出的NSException。 - Samuel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接