• iOS下KVO使用过程中的陷阱


    iOS下KVO使用过程中的陷阱

     

    KVO,全称为Key-Value Observing,是iOS中的一种设计模式,用于检测对象的某些属性的实时变化情况并作出响应。网上广为流传普及的一个例子是利用KVO检测股票价格的变动,例如这里。这个例子作为扫盲入门还是可以的,但是当应用场景比较复杂时,里面的一些细节还是需要改进的,里面有多个地方存在crash的危险。本文旨在逐步递进深入地探讨出一种目前比较健壮稳定的KVO实现方案,弥补网上大部分教程的不足!

    首先,假设我们的目标是在一个UITableViewController内对tableview的contentOffset进行实时监测,很容易地使用KVO来实现为。

    在初始化方法中加入:

    1
    [_tableView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];

    在dealloc中移除KVO监听:

    1
    [_tableView removeObserver:self forKeyPath:@"contentOffset" context:nil];

    添加默认的响应回调方法:

    1
    2
    3
    4
    5
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
                            change:(NSDictionary *)change context:(void *)context
    {
           [self doSomethingWhenContentOffsetChanges];
    }

    好了,KVO实现就到此完美结束了,拜拜。。。开个玩笑,肯定没这么简单的,这样的代码太粗糙了,当你在controller中添加多个KVO时,所有的回调都是走同上述函数,那就必须对触发回调函数的来源进行判断。判断如下:

    1
    2
    3
    4
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
                            change:(NSDictionary *)change context:(void *)context
    {
        if (object == _tableView && [keyPath isEqualToString:@"contentOffset"]) {
    1
    [self doSomethingWhenContentOffsetChanges];
    1
    } }

    你以为这样就结束了吗?答案是否定的!我们假设当前类(在例子中为UITableViewController)还有父类,并且父类也有自己绑定了一些其他KVO呢?我们看到,上述回调函数体中只有一个判断,如果这个if不成立,这次KVO事件的触发就会到此中断了。但事实上,若当前类无法捕捉到这个KVO,那很有可能是在他的superClass,或者super-superClass...中,上述处理砍断了这个链。合理的处理方式应该是这样的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
                            change:(NSDictionary *)change context:(void *)context
    {
        if (object == _tableView && [keyPath isEqualToString:@"contentOffset"]) {
            [self doSomethingWhenContentOffsetChanges];
    else {
            [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
    }

    这样就结束了吗?答案仍旧是否定的。潜在的问题有可能出现在dealloc中对KVO的注销上。KVO的一种缺陷(其实不能称为缺陷,应该称为特性)是,当对同一个keypath进行两次removeObserver时会导致程序crash,这种情况常常出现在父类有一个kvo,父类在dealloc中remove了一次,子类又remove了一次的情况下。不要以为这种情况很少出现!当你封装framework开源给别人用或者多人协作开发时是有可能出现的,而且这种crash很难发现。不知道你发现没,目前的代码中context字段都是nil,那能否利用该字段来标识出到底kvo是superClass注册的,还是self注册的?

    回答是可以的。我们可以分别在父类以及本类中定义各自的context字符串,比如在本类中定义context为@"ThisIsMyKVOContextNotSuper";然后在dealloc中remove observer时指定移除的自身添加的observer。这样iOS就能知道移除的是自己的kvo,而不是父类中的kvo,避免二次remove造成crash。

  • 相关阅读:
    Elasticsearch Query DSL 整理总结(三)—— Match Phrase Query 和 Match Phrase Prefix Query
    Elasticsearch Query DSL 整理总结(二)—— 要搞懂 Match Query,看这篇就够了
    Elasticsearch Query DSL 整理总结(一)—— Query DSL 概要,MatchAllQuery,全文查询简述
    Elasticsearch Java Rest Client API 整理总结 (三)——Building Queries
    Elasticsearch date 类型详解
    python 历险记(五)— python 中的模块
    python 历险记(四)— python 中常用的 json 操作
    python 历险记(三)— python 的常用文件操作
    Elasticsearch Java Rest Client API 整理总结 (二) —— SearchAPI
    Elasticsearch Java Rest Client API 整理总结 (一)——Document API
  • 原文地址:https://www.cnblogs.com/like359881584/p/5857819.html
Copyright © 2020-2023  润新知