was deallocated while key value observers were still registered with it.

今天在调用自己一个工具类的时候遇到了这个问题,大致的意思是注册了观察者,然后没有被注销掉,又开始重复使用了。也可以说 KVO 使用不当造成的,所以在此先来了解下 KVO。

通俗的说,KVO 就是我们是用来设值或取值的协议(NSKeyValueCoding)。通过对变量和函数名进行规范达到方便设置类成员值的目的。它是Cocoa的一个重要机制,它有点类似于Notification,但是,它提供了观察某一属性变化的方法,而Notification需要一个发送notification的对象,这样KVO就比Notification极大的简化了代码。

KVO 如何工作的呢?
  • 1、两个对象,其中一个对象的属性发生改变的时候,另一个对象可以监测到。
// 对象(被观察者)
@interface Student : NSObject
// 观察者
@interface StudentObserver : NSObject
    
  • 2、两个对象间通过 ”addObserver:forKeyPath:options:context:“建立起连接
- (void)addObserver:(NSObject *)anObserver
         forKeyPath:(NSString *)keyPath
            options:(NSKeyValueObservingOptions)options
            context:(void *)context
  • 3、为了能够响应消息,作为观察者的对象必须实现下面这个方法。这个方法实现如何响应变化的消息。
- (void)observeValueForKeyPath:(NSString *)keyPath
                     ofObject:(id)object
                       change:(NSDictionary<NSString *,id> *)change
                      context:(void *)context
  • 4、假如遵循KVO规则的话,当被观察的对象的属性改变的时候,就会直接调用上面那个方法啦,同时移除掉对观察者的监听。
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context;

上述案例的完整代码

#import <Foundation/Foundation.h>

@interface Student : NSObject

@property (nonatomic, copy) NSString *name;

@end

#import "StudentObserver.h"

@implementation StudentObserver

- (void)observeValueForKeyPath:(NSString *)keyPath
                     ofObject:(id)object
                       change:(NSDictionary<NSString *,id> *)change
                      context:(void *)context {
    
    NSLog(@"old = %@",[change objectForKey:NSKeyValueChangeOldKey]);
    NSLog(@"new = %@",[change objectForKey:NSKeyValueChangeNewKey]);
    NSLog(@"context:%@",context);
    
}

@end
Student *student = [[Student alloc] init];
student.name = @"yang";
StudentObserver *studentObserver = [[StudentObserver alloc] init];

[student addObserver:studentObserver
           forKeyPath:@"name"
              options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld
              context:(void*)self];

student.name = @"liu";
[student removeObserver:studentObserver forKeyPath:@"name"];
/**
 *  input
        old = yang
        new = liu
        context:<ViewController: 0x7f98c84a0720>
 */

通过上面阐述的,我们大致了解了 KVO可以很好的观察某一属性变化的方法。

详细了解下下面几个方法

注册观察者方法

- (void)addObserver:(NSObject *)anObserver
         forKeyPath:(NSString *)keyPath
            options:(NSKeyValueObservingOptions)options
            context:(void *)context
* anObserver:观察者对象,这个对象必须实现observeValueForKeyPath:ofObject:change:context:方法,以响应属性的修改通知。
* keyPath:被监听的属性。这个值不能为nil。
* options:监听选项,这个值可以是NSKeyValueObservingOptions选项的组合。关于监听选项,我们会在下面介绍。
* context:任意的额外数据,我们可以将这些数据作为上下文数据,它会传递给观察者对象的observeValueForKeyPath:ofObject:change:context:方法。这个参数的意义在于用于区分同一对象监听同一属性(从属于同一对象)的多个不同的监听

// options
typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {
   // 提供属性的新值
    NSKeyValueObservingOptionNew = 0x01,
     // 提供属性的旧值
    NSKeyValueObservingOptionOld = 0x02,
   // 如果指定,则在添加观察者的时候立即发送一个通知给观察者,        
    // 并且是在注册观察者方法返回之前
    NSKeyValueObservingOptionInitial NS_ENUM_AVAILABLE(10_5, 2_0) = 0x04,
    // 如果指定,则在每次修改属性时,会在修改通知被发送之前预先发送一条通知给观察者
    // 这与-willChangeValueForKey:被触发的时间是相对应的。 
    // 这样,在每次修改属性时,实际上是会发送两条通知。
    NSKeyValueObservingOptionPrior NS_ENUM_AVAILABLE(10_5, 2_0) = 0x08

};

处理属性观察者方法

- (void)observeValueForKeyPath:(NSString *)keyPath
                     ofObject:(id)object
                       change:(NSDictionary<NSString *,id> *)change
                      context:(void *)context
keyPath:即被观察的属性,与参数object相关。
object:keyPath所属的对象。
change:这是一个字典,它包含了属性被修改的一些信息。这个字典中包含的值会根据我们在添加观察者时设置的options参数的不同而有所不同。
context:这个值即是添加观察者时提供的上下文信息。
// change key
FOUNDATION_EXPORT NSString *const NSKeyValueChangeKindKey;
FOUNDATION_EXPORT NSString *const NSKeyValueChangeNewKey;
FOUNDATION_EXPORT NSString *const NSKeyValueChangeOldKey;
FOUNDATION_EXPORT NSString *const NSKeyValueChangeIndexesKey;
FOUNDATION_EXPORT NSString *const NSKeyValueChangeNotificationIsPriorKey NS_AVAILABLE(10_5, 2_0);

再通过一个我们可能用到的例子加深理解,监听tableView的 contentOffset 中的偏移量。

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.tableView addObserver: self
                     forKeyPath: @"contentOffset"
                        options: NSKeyValueObservingOptionNew
                        context: nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSString *,id> *)change
                       context:(void *)context {
    CGFloat offset = self.tableView.contentOffset.y;
    NSLog(@"self.table.contentOffset === %f",offset);
}
- (void)dealloc {
    [self.tableView removeObserver:self
                        forKeyPath:@"contentOffset"
                           context:nil];
}

像dealloc 中最好是加一个 @try异常捕获的写法,更安全。

- (void)dealloc {
    @try {
        [self.item removeObserver:self forKeyPath:@"content"];
    }
    @catch (NSException *exception) {
         NSLog(@"Exception: %@", exception); 
    }
    @finally  { 
        // Added to show finally works as well 
    }
}

总得说来,KVO 很强大,也有很多坑,更详细的了解可以仔细看看Foundation: NSKeyValueObserving(KVO),至此笔记先到这。

回过头了,上述那个问题,还是没有说怎么解决,首先要说明的是使用 KVO 需要小心,需要养成好习惯。上述那问题就是提示我们注册了观察者不要忘记了注销,可以尝试在 dealloc 中注销下试试。

备注

http://zhangbuhuai.com/2015/04/29/understanding-KVO/
http://southpeak.github.io/blog/2015/04/23/cocoa-foundation-nskeyvalueobserving/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,784评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,745评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,702评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,229评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,245评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,376评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,798评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,471评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,655评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,485评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,535评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,235评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,793评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,863评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,096评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,654评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,233评论 2 341

推荐阅读更多精彩内容