Objective-C KVO

此文实际成于 2015/08/11

KVO 与实例变量

如何正确的对实例变量进行 KVO 编程?

下面是一个简单的带有一个实例变量的类。

@interface KVOObject : NSObject{
@public
    NSString *_foo;
}
@end

@implementation KVOObject  @end

然后是一个测试类,进行 KVO

@interface KVOObjectDemo : NSObject
@property (nonatomic,strong) KVOObject *kvoObject;
@end

@interface KVOObjectDemo : NSObject
@property (nonatomic,strong) KVOObject *kvoObject;
@end

NSString * const fooKeyPath = @"_foo";

@implementation KVOObjectDemo

- (instancetype)init
{
    self = [super init];
    if (self) {
        _kvoObject = [KVOObject new];
        [_kvoObject addObserver:self forKeyPath:fooKeyPath options:NSKeyValueObservingOptionNew context:nil];
    }
    return self;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    NSLog(@"%s keyPath:%@",__FUNCTION__,keyPath);
    if ([fooKeyPath isEqualToString:keyPath]) {
        NSLog(@"foo value changed to \"%@\"  instance value:\"%@\"",change[NSKeyValueChangeNewKey],_kvoObject->_foo);
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

- (void)dealloc
{
    printf("dealloc");
    [_kvoObject removeObserver:self forKeyPath:fooKeyPath];
}

@end

测试代码:

         KVOObjectDemo *kvodemo = [KVOObjectDemo new];
         kvodemo.kvoObject->_foo = @"good";
         [kvodemo.kvoObject setValue:@"really cool" forKey:fooKeyPath];
  1. 首先是设置 fooKeyPath_foo
    运行输出结果如下 :
2015-08-11 08:41:06.684 ObjcDemo[34376:17203392] -[KVOObjectDemo observeValueForKeyPath:ofObject:change:context:] keyPath:_foo
2015-08-11 08:41:06.684 ObjcDemo[34376:17203392] foo value changed to "really cool"  instance value:"really cool"
dealloc

结果显示 KVO 编程成功。

  1. 再设置 fooKeyPathfoo
    运行输出结果如下 :
2015-08-11 08:43:16.816 ObjcDemo[34426:17204277] -[KVOObjectDemo observeValueForKeyPath:ofObject:change:context:] keyPath:foo
2015-08-11 08:43:16.817 ObjcDemo[34426:17204277] foo value changed to "really cool"  instance value:"really cool"
dealloc

结果也是 OK 的。

查看文档发现 setValue:forKey: 帮我们做了大量的事情。

setValue:foKey: 的默认搜索顺序

  1. 搜索接收类 与 set: 匹配的 setter
  2. 如果没有找到 setter 并且类方法 accessInstanceVariableDirectly 返回 YES (此类方法,默认返回 YES)
    搜索接收类中实例变量的名字与 _,_is,,is 匹配的实例变量。
  1. 如果匹配的 setter 或者实例变量找到了,将用于赋值。如果有必要的话,
    值将以在Representing Non-Object Values 讨论的方式从对象中抽取出来。

  2. 如果都没有搜索到,将调用接收者的 setValue:forUndefinedKey:, 此方法的默认实现是抛出 NSUndefinedKeyException ,你可以重写些方法。

  3. 如果有对应的 setter,并且如果参数类型不是一个对象指针,但是值为 nil,此时将调用。 -setNilValueForKey:
    其默认实现是抛出NSInvalidArgumentException.但你可以重写此方法。如果是其他类型,在调用相应方法时 NSNumber/NSValue 转换会提前进行。

参考: Accessor Search Implementation Details

KVO 的几个选项

public struct NSKeyValueObservingOptions:OptionSetType{
    public static var New: NSKeyValueObservingOptions { get }
    public static var Old: NSKeyValueObservingOptions { get }
    @available(iOS 2.0, *)
    public static var Initial: NSKeyValueObservingOptions { get }
    @available(iOS 2.0, *)
    public static var Prior: NSKeyValueObservingOptions { get }
}

在 observe 回调方法:

    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer) {
   }

有这么一个参数: change: [String : AnyObject]?
他是一个字典。我们知道 我们使用 KVO 就是想当某个值变化的时候通知我们。告诉我们值的变化。
如果 KVO 选项中带有 .New 表示变更列表中需要包含新的值,
.Old 表示变更列表中带上新的值。

.Initial 这个选项是新加的, 这是用来处理这种场景的,就是当我们添加此 observer时是否应该马上
调用的observeValueForKeyPath:.... 这个函数。来告诉此方法要感兴趣的的属性现在的值。

一些错误

注册了某一个KeyPath 但是没有处理会报错

super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)

2015-11-16 12:13:17.554 BXLoadMoreControl_Example[9020:15633890] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: ': An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Key path: contentOffset
Observed object: ; layer = ; contentOffset: {0, 0}; contentSize: {600, 0}>

Observer 没有移除,但是对象已经被释放

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x15e44460 of class AVAudioPlayer was deallocated while key value observers were still registered with it. Current observation info: (
Context: 0x15d71ba8, Property: 0x15ea1770>

你可能感兴趣的:(Objective-C KVO)