聊聊 KVO

引用:http://khanlou.com/2013/12/kvo-considered-harmful/

细数KVO的弊端:

  1. 所有实现都在同一个方法里调用
 - (void)addObserver: (NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context 

比如说我需要观察tablview的contentsize属性,我这样来写:

 [_tableView addObserver:self forKeyPath:@"contentSize" options:0 context:NULL];  

 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {  
    [self configureView];  
}  

完成了?看起来很简单啊,so young,为了写这个,我们还需要做些额外的工作 orz

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {  
    if (object == _tableView && [keyPath isEqualToString:@"contentSize"]) {  
        [self configureView];  
    }  
} 
  1. KVO is string-ly typed
    keypath ‘contentSiz’ 是字符串类型的,这就意味着编译器跟解析器不会告诉你这个属性是什么类型的或者存不存在。它只是一个字符串。我们只能使用NSStringFromSelector(@selector(contentSize))来让编译器告诉我们这个存不存在。
    另外,当我们观察一个view controller时,想要获取它scrollview的contentsize,这时候keypathscrollview.contentOffset。这种情况下我们能做的事就更少了。
  2. KVO 必须处理父类实现
    我们也许还有一个父类也在监听,并实现了这个接口方法,那我们就该调用super方法
    if (object == _tableView && [keyPath isEqualToString:@"contentSize"]) {  
        [self configureView];  
    } else {  
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];  
    }  
} 

如果我们一不小心忘了,那就可能导致父类监听失效。
所以保险起见,我们最好不嫌麻烦地调用super方法 orz。

  1. KVO 在解除注册的时候可能导致crash
    我们一般会在-dealloc[_tableView removeObserver:self forKeyPath:NSStringFromSelector(@selector(contentSize)) context:NULL];
    但值得注意的是,tableview有可能会dealloc两次,这种情况下因为我们尝试remove同一个observance两次,就可能导致我们的app crash掉。另外,当父类也在监听同一属性的时候,也可能会调用两次导致crash。
    这个时候是该用到 context ,我们可能会context 当做 self来用,但在父类里,就不好用了。 (因为self不管在父类还是子类中都会指向同一个对象)所以推荐使用静态指针来存储context。
static void *ClassNameTableViewContentSizeContext = &ClassNameTableViewContentSizeContext;  
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {  
   if (context == ClassNameTableViewContentSizeContext) {  
       [self doThing];  
   } else if (context == OtherContext) {  
       [self doOtherThing];  
   } else {  
       [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];  
   }  
}  
  1. KVO太难调试
    delegate调试时,很容易就追踪到设置它的对象了,但KVO则可能需要在运行时使用 isKindOfClass:来追踪。
    这个就不用多举例子了,用过的人都有体会。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 写在前面 程序设计语言中有各种各样的设计模式(pattern)和与此对应的反设计模式(anti-pattern),...
    Frankxp阅读 4,966评论 0 23
  • 先聊聊 KVO 与 KVC 的区别吧:KVO是指键-值-观察者模式, 键值监听, 监听一个对象属性值的改变。KVO...
    smile丽语阅读 355评论 1 3
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,824评论 0 9
  • 上半年有段时间做了一个项目,项目中聊天界面用到了音频播放,涉及到进度条,当时做android时候处理的不太好,由于...
    DaZenD阅读 3,047评论 0 26
  • 夜风似影久等在门外 残月独挂在窗台 将回忆映白 怀那人还在不在 繁华的尘埃落下来 喧嚣散去忘记了悲哀 用一生缘分等...
    花開淺夏阅读 482评论 0 1