2

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
Kamchatka
  • 3,597
  • 4
  • 38
  • 69

0 Answers0