The following code results in an NSInternalConsistencyException: Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x7fd3a1010000 of class UIScrollView was deallocated while key value observers were still registered with it.
.
Practically, the issue is that when dealloc
is called on Observer, the reference it holds to UIScrollView has already been nillified, and the _removeObservers
is a no-op.
I understand that this might have to do with the way the associated objects are deallocated, but this is actually not clear from https://developer.apple.com/videos/play/wwdc2011-322/ (38:14) where the associated objects are erased before the weak reference are erased.
A workaround I have found is to make the UIScrollView property unsafe_unretained
.
- Does someone know precisely why this happens?
- What is the recommended solution here?
Thanks!
@interface Observer : NSObject
@property (nonatomic, weak) UIScrollView *scrollView;
@end
@implementation Observer
- (void)dealloc {
[self _removeObservers];
}
- (void)setScrollView:(UIScrollView *)scrollView {
if (_scrollView) {
[self _removeObservers];
}
_scrollView = scrollView;
[self.scrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {}
- (void)_removeObservers {
[self.scrollView removeObserver:self forKeyPath:@"contentOffset"];
}
@end
@interface UIScrollView (Test)
@end
@implementation UIScrollView (Test)
- (void)test_setup {
Observer *observer = [Observer new];
observer.scrollView = self;
objc_setAssociatedObject(self, @"observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
And then:
UIScrollView *scrollView = [UIScrollView new];
[scrollView test_setup];
// scrollView is deallocated <--- EXCEPTION