KVO

2018-07-01  本文已影响2人  紫荆秋雪_文

一、KVO介绍

KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变

二、KVO实例

通过一个简单的例子来演示一下KVO,首先创建一个RevanPerson类,并且定义一个age属性,我们通过KVO来监听age值的改变

/*************** RevanPerson.h ***************/
#import <Foundation/Foundation.h>

@interface RevanPerson : NSObject
@property (nonatomic, assign) NSInteger age;

@end

/*************** RevanPerson.m ***************/
#import "RevanPerson.h"

@implementation RevanPerson

@end

/************* KVO测试代码 ***************/
#import "ViewController.h"
#import "RevanPerson.h"

@interface ViewController ()
@property (nonatomic, strong) RevanPerson *revanP1;

@end

@implementation ViewController

- (void)dealloc {
    [self.revanP1 removeObserver:self forKeyPath:@"age"];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    //revanP1
    self.revanP1 = [[RevanPerson alloc] init];
    self.revanP1.age = 11;
    //策略
    NSKeyValueObservingOptions options =  NSKeyValueObservingOptionNew |
    NSKeyValueObservingOptionOld;
    //给self.revanP1添加一个监听者:self
    [self.revanP1 addObserver:self forKeyPath:@"age" options:options context:nil];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.revanP1.age = 12;
}

#pragma mark - KVO的监听方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"\nkeyPath=%@\nobject=%@\nchange=%@\n"
          ,keyPath
          ,object
          ,change);
}

@end

当点击屏幕会触发touchesBegan:withEvent:方法来修改age的大小,由于age值的改变会触发KVO的
监听方法observeValueForKeyPath:ofObject:change:
输出如下:
keyPath=age
object=<RevanPerson: 0x60400000fa20>
change={
    kind = 1;
    new = 12;//age改变后的新值
    old = 11;  //age改变前的旧值
}

三、KVO原理分析

1、为了分析KVO原理,重新创建一个revanP2实例,但是不给revanP2实例添加KVO

/*************** RevanPerson.h ***************/
#import <Foundation/Foundation.h>

@interface RevanPerson : NSObject
@property (nonatomic, assign) NSInteger age;

@end
/*************** RevanPerson.m ***************/
#import "RevanPerson.h"

@implementation RevanPerson

@end

/************* KVO测试代码 ***************/
#import "ViewController.h"
#import "RevanPerson.h"

@interface ViewController ()
@property (nonatomic, strong) RevanPerson *revanP1;
@property (nonatomic, strong) RevanPerson *revanP2;
@end

@implementation ViewController

- (void)dealloc {
    [self.revanP1 removeObserver:self forKeyPath:@"age"];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    //revanP1
    self.revanP1 = [[RevanPerson alloc] init];
    self.revanP1.age = 11;
    
    
    self.revanP2 = [[RevanPerson alloc] init];
    self.revanP2.age = 21;
    //策略
    NSKeyValueObservingOptions options =  NSKeyValueObservingOptionNew |
    NSKeyValueObservingOptionOld;
    //给self.revanP1添加一个监听者:self
    [self.revanP1 addObserver:self forKeyPath:@"age" options:options context:nil];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.revanP1.age = 12;
    self.revanP2.age = 22;
}

#pragma mark - KVO的监听方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"\nkeyPath=%@\nobject=%@\nchange=%@\n"
          ,keyPath
          ,object
          ,change);
}

打印输出:只有revanP1的输出
keyPath=age
object=<RevanPerson: 0x600000014cb0>
change={
    kind = 1;
    new = 12;
    old = 11;
}

在触发touchesBegan:withEvent:方法时会给revanP1实例对象和revanP2实例对象的age赋值,会触发age的setAge:方法。Objective-C对象的分类中说过,对象方法信息是存储在class对象中的,类方法信息是存储在meta-class对象中的。所以我们先查看一下revanP1、revanP2的class对象

/************* KVO测试代码 ***************/
#import "ViewController.h"
#import "RevanPerson.h"
#import <objc/runtime.h>

@interface ViewController ()
@property (nonatomic, strong) RevanPerson *revanP1;
@property (nonatomic, strong) RevanPerson *revanP2;
@end

@implementation ViewController

- (void)dealloc {
    [self.revanP1 removeObserver:self forKeyPath:@"age"];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    //revanP1
    self.revanP1 = [[RevanPerson alloc] init];
    self.revanP1.age = 11;
    
    
    self.revanP2 = [[RevanPerson alloc] init];
    self.revanP2.age = 21;
    
    Class revanP1_class = object_getClass(self.revanP1);
    Class revanP2_class = object_getClass(self.revanP2);
    NSLog(@"\nrevanP1KVO之前的class对象:%p-%@", revanP1_class, revanP1_class);
    NSLog(@"\nrevanP2的class对象:%p-%@", revanP2_class, revanP2_class);
    //策略
    NSKeyValueObservingOptions options =  NSKeyValueObservingOptionNew |
    NSKeyValueObservingOptionOld;
    //给self.revanP1添加一个监听者:self
    [self.revanP1 addObserver:self forKeyPath:@"age" options:options context:nil];
    Class revanP1_class_kvo = object_getClass(self.revanP1);
    NSLog(@"\nrevanP1KVO之后的class对象:%p-%@", revanP1_class_kvo, revanP1_class_kvo);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.revanP1.age = 12;
    self.revanP2.age = 22;
}

#pragma mark - KVO的监听方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"\nkeyPath=%@\nobject=%@\nchange=%@\n"
          ,keyPath
          ,object
          ,change);
}

@end

打印输出:
revanP1KVO之前的class对象:0x10b0dc0b8-RevanPerson
revanP2的class对象:0x10b0dc0b8-RevanPerson
revanP1KVO之后的class对象:0x604000116140-NSKVONotifying_RevanPerson
/************* KVO测试代码 ***************/
#import "ViewController.h"
#import "RevanPerson.h"
#import <objc/runtime.h>

@interface ViewController ()
@property (nonatomic, strong) RevanPerson *revanP1;
@property (nonatomic, strong) RevanPerson *revanP2;
@end

@implementation ViewController

- (void)dealloc {
    [self.revanP1 removeObserver:self forKeyPath:@"age"];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    //revanP1
    self.revanP1 = [[RevanPerson alloc] init];
    self.revanP1.age = 11;
    
    
    self.revanP2 = [[RevanPerson alloc] init];
    self.revanP2.age = 21;
    
    Class revanP1_class = object_getClass(self.revanP1);
    Class revanP2_class = object_getClass(self.revanP2);
    NSLog(@"\nrevanP1KVO之前的class对象:%p-%@", revanP1_class, revanP1_class);
    NSLog(@"\nrevanP2的class对象:%p-%@", revanP2_class, revanP2_class);
    //策略
    NSKeyValueObservingOptions options =  NSKeyValueObservingOptionNew |
    NSKeyValueObservingOptionOld;
    //给self.revanP1添加一个监听者:self
    [self.revanP1 addObserver:self forKeyPath:@"age" options:options context:nil];
    Class revanP1_class_kvo = object_getClass(self.revanP1);
    NSLog(@"\nrevanP1KVO之后的class对象:%p-%@", revanP1_class_kvo, revanP1_class_kvo);
    Class revanP1_superclass_kvo = [revanP1_class_kvo superclass];
    NSLog(@"\nrevanP1KVO之后的superclass对象:%p-%@", revanP1_superclass_kvo, revanP1_superclass_kvo);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.revanP1.age = 12;
    self.revanP2.age = 22;
}

#pragma mark - KVO的监听方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"\nkeyPath=%@\nobject=%@\nchange=%@\n"
          ,keyPath
          ,object
          ,change);
}

@end

打印输出:
revanP1KVO之前的class对象:0x1074dd0e0-RevanPerson
revanP2的class对象:0x1074dd0e0-RevanPerson
revanP1KVO之后的class对象:0x60000011c170-NSKVONotifying_RevanPerson
revanP1KVO之后的superclass对象:0x1074dd0e0-RevanPerson

2、NSKVONotifying_RevanPerson分析

在revanP1添加KVO的时候,系统会通过runtime动态的创建一个NSKVONotifying_RevanPerson类,并且把revanP1的isa指针指向NSKVONotifying_RevanPerson的class对象。当在给age赋值的时候其实是调用了age的setAge:方法,revanP2会直接调用RevanPerson中的setAge:方法,假设NSKVONotifying_RevanPerson类中没有重写setAge:方法,那么给revanP1.age赋值和给revanP2.age赋值的效果是一样的了,但是实际的情况是没有添加KVO的revanP2在给age赋值的时候是不会触发observeValueForKeyPath:ofObject:change:监听方法,所以猜测NSKVONotifying_RevanPerson类中重写了age的setAge:方法

/************* KVO测试代码 ***************/
#import "ViewController.h"
#import "RevanPerson.h"
#import <objc/runtime.h>

@interface ViewController ()
@property (nonatomic, strong) RevanPerson *revanP1;
@property (nonatomic, strong) RevanPerson *revanP2;
@end

@implementation ViewController

- (void)dealloc {
    [self.revanP1 removeObserver:self forKeyPath:@"age"];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    //revanP1
    self.revanP1 = [[RevanPerson alloc] init];
    self.revanP1.age = 11;
    
    
    self.revanP2 = [[RevanPerson alloc] init];
    self.revanP2.age = 21;
    
    Class revanP1_class = object_getClass(self.revanP1);
    Class revanP2_class = object_getClass(self.revanP2);
    IMP revanP1_imp = [self.revanP1 methodForSelector:@selector(setAge:)];
    IMP revanP2_imp = [self.revanP1 methodForSelector:@selector(setAge:)];

//    NSLog(@"\nrevanP1KVO之前的class对象:%p-%@", revanP1_class, revanP1_class);
//    NSLog(@"\nrevanP2的class对象:%p-%@", revanP2_class, revanP2_class);
    NSLog(@"\nrevanP1KVO之前setAge:地址:%p", revanP1_imp);
    NSLog(@"\nrevanP2的setAge:地址:%p", revanP2_imp);
    
    
    //策略
    NSKeyValueObservingOptions options =  NSKeyValueObservingOptionNew |
    NSKeyValueObservingOptionOld;
    //给self.revanP1添加一个监听者:self
    [self.revanP1 addObserver:self forKeyPath:@"age" options:options context:nil];
    
    
    Class revanP1_class_kvo = object_getClass(self.revanP1);
    Class revanP1_superclass_kvo = [revanP1_class_kvo superclass];
    IMP revanP1_imp_kvo = [self.revanP1 methodForSelector:@selector(setAge:)];
    
//    NSLog(@"\nrevanP1KVO之后的class对象:%p-%@", revanP1_class_kvo, revanP1_class_kvo);
//    NSLog(@"\nrevanP1KVO之后的superclass对象:%p-%@", revanP1_superclass_kvo, revanP1_superclass_kvo);
    NSLog(@"\nrevanP1KVO之后setAge:地址:%p", revanP1_imp_kvo);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.revanP1.age = 12;
    self.revanP2.age = 22;
}

#pragma mark - KVO的监听方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"\nkeyPath=%@\nobject=%@\nchange=%@\n"
          ,keyPath
          ,object
          ,change);
}

@end
打印输出:
revanP1KVO之前setAge:地址:0x1021317c0
revanP2的setAge:地址:0x1021317c0
revanP1KVO之后setAge:地址:0x102477bf4
/*************** RevanPerson.h ***************/
#import <Foundation/Foundation.h>

@interface RevanPerson : NSObject
@property (nonatomic, assign) NSInteger age;

@end

/*************** RevanPerson.m ***************/
#import "RevanPerson.h"

@implementation RevanPerson

@end

/************* KVO测试代码 ***************/
#import "ViewController.h"
#import "RevanPerson.h"
#import <objc/runtime.h>

@interface ViewController ()
@property (nonatomic, strong) RevanPerson *revanP1;
@property (nonatomic, strong) RevanPerson *revanP2;
@end

@implementation ViewController

- (void)dealloc {
    [self.revanP1 removeObserver:self forKeyPath:@"age"];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    //revanP1
    self.revanP1 = [[RevanPerson alloc] init];
    self.revanP1.age = 11;
    
    self.revanP2 = [[RevanPerson alloc] init];
    self.revanP2.age = 21;
    
    Class revanP1_class = object_getClass(self.revanP1);
    Class revanP2_class = object_getClass(self.revanP2);
    NSLog(@"KVO之前revanP1,class对象中对象方法信息");
    [self methodNamesOfClass:revanP1_class];
    NSLog(@"KVO之前revanP2,class对象中对象方法信息");
    [self methodNamesOfClass:revanP2_class];
    
    //策略
    NSKeyValueObservingOptions options =  NSKeyValueObservingOptionNew |
    NSKeyValueObservingOptionOld;
    //给self.revanP1添加一个监听者:self
    [self.revanP1 addObserver:self forKeyPath:@"age" options:options context:nil];
    
    Class revanP1_class_kvo = object_getClass(self.revanP1);
    NSLog(@"KVO之后revanP1,class对象中对象方法信息");
    [self methodNamesOfClass:revanP1_class_kvo];
    NSLog(@"KVO之后revanP2,class对象中对象方法信息");
    [self methodNamesOfClass:revanP2_class];

}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.revanP1.age = 12;
    self.revanP2.age = 22;
}

#pragma mark - KVO的监听方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"\nkeyPath=%@\nobject=%@\nchange=%@\n"
          ,keyPath
          ,object
          ,change);
}

- (void)methodNamesOfClass:(Class)cls
{
    unsigned int count;
    // 获得方法数组
    Method *methodList = class_copyMethodList(cls, &count);
    
    // 存储方法名
    NSMutableString *methodNames = [NSMutableString string];
    
    // 遍历所有的方法
    for (int i = 0; i < count; i++) {
        // 获得方法
        Method method = methodList[i];
        // 获得方法名
        NSString *methodName = NSStringFromSelector(method_getName(method));
        // 拼接方法名
        [methodNames appendString:methodName];
        [methodNames appendString:@", "];
    }
    
    // 释放
    free(methodList);
    
    // 打印方法名
    NSLog(@"%@ %@", cls, methodNames);
}

@end

打印输出:
KVO之前revanP1,class对象中对象方法信息:
RevanPerson setAge:, age,
KVO之前revanP2,class对象中对象方法信息:
RevanPerson setAge:, age,
KVO之后revanP1,class对象中对象方法信息:
NSKVONotifying_RevanPerson setAge:, class, dealloc, _isKVOA,
KVO之后revanP2,class对象中对象方法信息:
RevanPerson setAge:, age,

四、小结

上一篇下一篇

猜你喜欢

热点阅读