iOS-KVO的原理

KVO全称为Key-Value Observing, 即键值监听,用于指定对象属性值的改变。

 

问题一、iOS用什么方式实现对一个对象的KVO(KVO的本质是什么?)

  • 利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
  • 当修改instance对象的属性时,会调用Foundation_NSSetXXXValueAndNotify函数,这个函数内部调用的方法如下
  1. willChangeValueForKey:
  2. 父类原来的setter
  3. didChangeValueForKey:
  4. didChangeValueForKey内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:

 

问题二、如何手动触发KVO

  • 手动调用willChangeValueForKey:didChangeValueForKey:

 

问题三、直接修改成员变量会触发KVO么?_age = 10;

  • 不会触发KVO 因为其内部是重写set方法来达到监听的

 

上面的问题 下面都会一一作出解释

 

定义如下一个Person类:

@interface Person : NSObject
@property (assign, nonatomic) int age;
@end

给在ViewController.h 里面给Person类的instance对象添加KVO

#import "ViewController.h"
#import "MJPerson.h"

@interface ViewController ()
@property (strong, nonatomic) MJPerson *person1;
@property (strong, nonatomic) MJPerson *person2;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.person1 = [[MJPerson alloc] init];
    self.person1.age = 1;
    
    self.person2 = [[MJPerson alloc] init];
    self.person2.age = 2;
    
    // 给person1对象添加KVO监听
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//    self.person1.age = 21;
//    self.person2.age = 22;
    
    // NSKVONotifying_MJPerson是使用Runtime动态创建的一个类,是MJPerson的子类
    
    // self.person1.isa == NSKVONotifying_MJPerson
    [self.person1 setAge:21];
    
    // self.person2.isa = MJPerson
    [self.person2 setAge:22];
}

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

// 当监听对象的属性值发生改变时,就会调用
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}

@end

 

当给Person的instance对象添加KVO监听后 内部发生如下的变化

1.Runtime会动态创建一个名为NSKVONotifying_Person的Person子类。
2.并将instance对象的isa指针指向这个子类Class。
3.并重写了属性的set方法 里面的实现如下 

  1. 调用 willChangeValueForKey: 
  2.  [super setAge:age] 
  3. 调用didChangeValueForKey: 里面会去通知监听器,属性值发生了改变

NSKVONotifying_Person类的setAge:方法的伪代码如下:

- (void)setAge:(int)age
{
    //调用Foundation框架的_NSSet***ValueAndNotify方法,与具体参数类型有关
    _NSSetIntValueAndNotify();
}

void _NSSetIntValueAndNotify()
{
    [self willChangeValueForKey:@"age"];
    [super setAge:age];
    [self didChangeValueForKey:@"age"];
}

- (void)didChangeValueForKey:(NSString *)key
{
    // 通知监听器,属性值发生了改变
    [oberser observeValueForKeyPath:key ofObject:self change:nil context:nil];
}

NSKVONotifying_Person 里面还重写了如下方法: 

// 屏蔽内部实现,隐藏了NSKVONotifying_Person类的存在
- (Class)class
{
    return [Person class];
}

- (void)dealloc
{
    // 收尾工作
}

- (BOOL)_isKVOA
{
    return YES;
}

 

如何知道里面还有这些方法呢 我们可以用runtime 获取打印下

#import "ViewController.h"
#import "MJPerson.h"
#import <objc/runtime.h>

@interface ViewController ()
@property (strong, nonatomic) MJPerson *person1;
@property (strong, nonatomic) MJPerson *person2;
@end

//@implementation NSObject
//
//- (Class)class
//{
//    return object_getClass(self);
//}
//
//@end

// 反编译工具 - Hopper

@implementation ViewController

- (void)printMethodNamesOfClass:(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);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.person1 = [[MJPerson alloc] init];
    self.person1.age = 1;
    
    self.person2 = [[MJPerson alloc] init];
    self.person2.age = 2;
    
    // 给person1对象添加KVO监听
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
    
    [self printMethodNamesOfClass:object_getClass(self.person1)];
    [self printMethodNamesOfClass:object_getClass(self.person2)];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//    [self.person1 setAge:21];
    //手动触发KVO 调用willChangeValueForKey 和 didChangeValueForKey
    [self.person1 willChangeValueForKey:@"age"];
    [self.person1 didChangeValueForKey:@"age"];
}

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

// observeValueForKeyPath:ofObject:change:context:
// 当监听对象的属性值发生改变时,就会调用
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}

@end

打印如下

 

 

 

 

 

 

 

由此可见,直接修改成员变量的值不会触发KVO。

posted @ 2020-11-23 20:31  俊华的博客  阅读(722)  评论(0编辑  收藏  举报