美文网首页
KVO的使用

KVO的使用

作者: 云天涯丶 | 来源:发表于2018-04-13 14:35 被阅读34次

一、介绍

KVO(NSKeyValueObserving):是一种非正式协议,当被观察的对象(比如A)的属性(比如name)改变时,观察者(比如VC)就会得到通知,然后做出相应处理。

NSObject提供了NSKeyValueObserving协议的实现,所以几乎所有的类都可以使用KVO。

KVO 的实现依赖于 Objective-C 强大的 Runtime, Apple 的文档对 KVO 机制的实现说的很简单:KVO是用isa-swizzling技术实现的,当观察者注册了对象的属性时,被观察对象的isa指针被修改,指向中间类而不是真实类...

当某个类的对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的 setter 方法。

派生类在被重写的 setter 方法实现真正的通知机制(手动实现键值观察)。这么做是基于设置属性会调用 setter 方法,而通过重写就获得了 KVO 需要的通知机制。当然前提是要通过遵循 KVO 的属性设置方式来变更属性值,如果仅是直接修改属性对应的成员变量,是无法实现 KVO 的。

同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。然后系统将这个对象的 isa 指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调用就会调用重写的 setter,从而激活键值通知机制。此外,派生类还重写了 dealloc 方法来释放资源。

二、api

@interface NSObject(NSKeyValueObserving)
// 观察属性的通知方法
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;

@end

@interface NSObject(NSKeyValueObserverRegistration)
// 注册、移除 观察者 
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

@end

@interface NSArray<ObjectType>(NSKeyValueObserverRegistration)
// 注册、移除 观察者  NSArray
- (void)addObserver:(NSObject *)observer toObjectsAtIndexes:(NSIndexSet *)indexes forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer fromObjectsAtIndexes:(NSIndexSet *)indexes forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (void)removeObserver:(NSObject *)observer fromObjectsAtIndexes:(NSIndexSet *)indexes forKeyPath:(NSString *)keyPath;

// 注册、移除 观察者  NSArray
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

@end

@interface NSOrderedSet<ObjectType>(NSKeyValueObserverRegistration)
// 注册、移除 观察者  NSOrderedSet
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

@end

@interface NSSet<ObjectType>(NSKeyValueObserverRegistration)
// 注册、移除 观察者  NSSet
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

@end

@interface NSObject(NSKeyValueObserverNotification)
// 手动触发KVO时重要方法
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;

- (void)willChange:(NSKeyValueChange)changeKind valuesAtIndexes:(NSIndexSet *)indexes forKey:(NSString *)key;
- (void)didChange:(NSKeyValueChange)changeKind valuesAtIndexes:(NSIndexSet *)indexes forKey:(NSString *)key;

- (void)willChangeValueForKey:(NSString *)key withSetMutation:(NSKeyValueSetMutationKind)mutationKind usingObjects:(NSSet *)objects;
- (void)didChangeValueForKey:(NSString *)key withSetMutation:(NSKeyValueSetMutationKind)mutationKind usingObjects:(NSSet *)objects;

@end

@interface NSObject(NSKeyValueObservingCustomization)

// KVO 依赖键
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

// 返回yes:自动触发KVO;若要手动触发KVO,返回no
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;

@property (nullable) void *observationInfo NS_RETURNS_INNER_POINTER;

@end

三、例子
1、基本用法

Person类里有个name属性

- (void)dealloc{
    [self.p removeObserver:self forKeyPath:@"name" context:nil];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.p = [Person new];
    [self.p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    self.p.name = @"a";
    self.p.name = @"b";
    self.p.name = @"c";
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    
    NSLog(@"%@ %@ %@",keyPath,object,change);
}

NSKeyValueObservingOptions:有四个值,分别是
NSKeyValueObservingOptionNew = 0x01, // 新值
NSKeyValueObservingOptionOld = 0x02, // 旧值
NSKeyValueObservingOptionInitial = 0x04,// 注册通知也会触发
NSKeyValueObservingOptionPrior = 0x08 // 值修改前后触发

2、手动KVO
Person.m

@synthesize name = _name;

- (void)setName:(NSString *)name{
    [self willChangeValueForKey:@"name"];
    _name = name;
    [self didChangeValueForKey:@"name"];
}

- (NSString *)name{
    return _name;
}

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([key isEqualToString:@"name"]) {
        return NO;
    }
  return [super automaticallyNotifiesObserversForKey:key];
}

手动触发的话要重写automaticallyNotifiesObserversForKey方法,return NO,不然观察者的通知方法走两遍

3、KVO 依赖键
有时候一个属性的值依赖于另一对象中的一个或多个属性,如果这些属性中任一属性的值发生变更,被依赖的属性值也应当为其变更进行标记。

KVO 依赖键有两种方法:

+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key;
+ (NSSet<NSString *> *)keyPathsForValuesAffecting<Key>;

下面分别用这两个方法实现:

// Student有两个属性:age和school
@interface Student : NSObject

@property (nonatomic,assign) NSInteger age;
@property (nonatomic,copy) NSString *school;

@end


// Person类
@interface Person : NSObject

@property (nonatomic,copy) NSString *info;
@property (nonatomic,strong) Student *stu;

@end

@implementation Person

- (instancetype)init
{
    self = [super init];
    if (self) {
        _stu = [Student new];
    }
    return self;
}

- (NSString *)info{
    return [NSString stringWithFormat:@"小敏%ld岁了,在%@上学",_stu.age,_stu.school];
}

+ (NSSet<NSString *> *)keyPathsForValuesAffectingInfo{
    NSSet *keyPaths = [NSSet setWithObjects:@"stu.age",@"stu.school", nil];
    return keyPaths;
}

//+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
//
//    if ([key isEqualToString:@"info"]) {
//        return [NSSet setWithObjects:@"stu.age",@"stu.school", nil];
//    }
//
//    return [super keyPathsForValuesAffectingValueForKey:key];
//}

@end

// 调用
@interface ViewController ()

@property (nonatomic,strong) Person *p;

@end

@implementation ViewController

- (void)dealloc{
    [self.p removeObserver:self forKeyPath:@"info" context:nil];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.p = [Person new];
    
    [self.p addObserver:self forKeyPath:@"info" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"cc"];
    self.p.stu.age = 18;
    self.p.stu.school = @"北大";
    
    
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    // descriptionWithLocale 转码处理
    NSLog(@"%@ %@ %@",keyPath,object,[change descriptionWithLocale:nil]);
}

结果:

info <Person: 0x60c000036860> {
    kind = 1;
    new = "小敏18岁了,在(null)上学";
    old = "小敏0岁了,在(null)上学";
}
2018-04-13 14:19:49.729454+0800 test[5259:597332] info <Person: 0x60c000036860> {
    kind = 1;
    new = "小敏18岁了,在北大上学";
    old = "小敏18岁了,在(null)上学";
}

相关文章

  • KVO基本使用

    分三部分解释KVO一.KVO基本使用二.KVO原理解析三.自定义实现KVO 一、KVO基本使用 使用KVO,能够非...

  • KVO

    目录 1. KVO的使用1.1 KVO基本使用方法1.2 KVO手动触发模式1.3 KVO属性依赖1.4 KVO容...

  • iOS原理篇(一): KVO实现原理

    KVO实现原理 什么是 KVO KVO 基本使用 KVO 的本质 总结 一 、 什么是KVO KVO(Key-Va...

  • 如何优雅地使用 KVO

    如何优雅地使用 KVO 如何优雅地使用 KVO

  • KVO 原理探究

    [TOC] KVO 研究 没有使用KVO和使用KVO的变化 测试的类Person 通过 objc_copyClas...

  • [iOS] KVO的指导

    nshipster - KVO 如何优雅地使用 KVO

  • iOS-KVO

    一.kvo使用 kvo可以监听一个对象属性的变化,下面为简单使用. 二.使用runtime分析kvo 我写了个简单...

  • KVO如何才能直接监听到数组的变化

    转自关于使用KVO监听数组的问题 首先,数组不能直接使用KVO使用监听。当我们想要使用KVO监听数组的状态时改变然...

  • 20.iOS底层学习之KVO 原理

    本篇提纲1、KVO简介;2、KVO的使用;3、KVO的一些细节;4、KVO的底层原理; KVO简介 KVO全称Ke...

  • iOS 开发Tip2

    51.封装KVO 使用(注意:对UITextfiled使用在真机上不可以使用KVO,KVO是基于KVC,因为通过键...

网友评论

      本文标题:KVO的使用

      本文链接:https://www.haomeiwen.com/subject/gotckftx.html