美文网首页
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的使用

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