KVO的使用以及原理

iOS KVO的使用以及原理

简介

KVO:(Key - Value - Observer) 键值观察者,是观察者设计模式的一种具体实现(C层和M层的通信)

KVO触发机制:

一个对象(观察者),检测另一个对象(被观察者)的某属性是否发生变化,若被监测的属性发生了更改,会触发观察者的一个方法(方法名固定,类似代理方法)

KVO的使用

//
//  ViewController.m
//  kvoTest
//
//  Created by Admin on 2021/12/7.
//

#import "ViewController.h"
@interface Person:NSObject
@property (nonatomic,assign) int age;
@end

@implementation Person

@end

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    Person *p1 = [[Person alloc] init];
    p1.age = 1;
    // self 监听 p1的 age属性
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [p1 addObserver:self forKeyPath:@"age" options:options context:nil];
    p1.age = 10;
    [p1 removeObserver:self forKeyPath:@"age"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    NSLog(@"监听到%@的%@改变了%@", object, keyPath,change);
}

@end

使用方法

  • 注册观察者(为被观察这指定观察者以及被观察者属性)(addObserver: forKeyPath:)
  • 实现回调方法(observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context)
  • 触发回调方法(通过点语法或者set方法又或者KVC赋值)
  • 移除观察者(removeObserver: forKeyPath:)

KVO的原理

当一个对象使用了KVO监听,iOS系统会修改这个对象的isa指针,改为指向一个全新的通过Runtime动态创建的子类,子类拥有自己的set方法实现,set方法实现内部会顺序调用willChangeValueForKey方法、原来的setter方法实现、didChangeValueForKey方法,而didChangeValueForKey方法内部又会调用监听器的observeValueForKeyPath:ofObject:change:context:监听方法。

验证代码

//
//  ViewController.m
//  kvoTest
//
//  Created by Admin on 2021/12/7.
//

#import "ViewController.h"
#import 

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

@implementation Person

- (void)setAge:(int)age
{
    NSLog(@"setAge:");
    _age = age;
}
- (void)willChangeValueForKey:(NSString *)key
{
    NSLog(@"willChangeValueForKey: - begin");
    [super willChangeValueForKey:key];
    NSLog(@"willChangeValueForKey: - end");
}
- (void)didChangeValueForKey:(NSString *)key
{
    NSLog(@"didChangeValueForKey: - begin");
    [super didChangeValueForKey:key];
    NSLog(@"didChangeValueForKey: - end");
}
@end

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    Person *p1 = [[Person alloc] init];
    Person *p2 = [[Person alloc] init];
    p1.age = 1;
    // self 监听 p1的 age属性
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [p1 addObserver:self forKeyPath:@"age" options:options context:nil];
    [self printMethods: object_getClass(p2)];
    [self printMethods: object_getClass(p1)];
    p1.age = 10;
    [p1 removeObserver:self forKeyPath:@"age"];
}
- (void) printMethods:(Class)cls
{
    unsigned int count ;
    Method *methods = class_copyMethodList(cls, &count);
    NSMutableString *methodNames = [NSMutableString string];
    [methodNames appendFormat:@"%@ - ", cls];
    for (int i = 0 ; i < count; i++) {
        Method method = methods[i];
        NSString *methodName  = NSStringFromSelector(method_getName(method));
        [methodNames appendString: methodName];
        [methodNames appendString:@" "];
    }
    NSLog(@"%@",methodNames);
    free(methods);
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    NSLog(@"监听到%@的%@改变了%@", object, keyPath,change);
}

@end

KVO如何手动触发

被监听的属性的值被修改时,就会自动触发KVO。如果想要手动触发KVO,则需要我们自己调用willChangeValueForKeydidChangeValueForKey方法即可在不改变属性值的情况下手动触发KVO,并且这两个方法缺一不可

验证代码

- (void)viewDidLoad {
    [super viewDidLoad];
    Person *p1 = [[Person alloc] init];
    Person *p2 = [[Person alloc] init];
    p1.age = 1;
    // self 监听 p1的 age属性
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [p1 addObserver:self forKeyPath:@"age" options:options context:nil];
    
    [self printMethods: object_getClass(p2)];
    [self printMethods: object_getClass(p1)];
//    p1.age = 10;
    [p1 willChangeValueForKey:@"age"];
    [p1 didChangeValueForKey:@"age"];
    [p1 removeObserver:self forKeyPath:@"age"];
}

**willChangeValueForKeydidChangeValueForKey 它们分别做了什么?**
willChangeValueForKey是为了通过KVC方法取得旧值
didChangeValueForKey中会调用监听器的监听方法,最终来到监听者的observeValueForKeyPath方法中

你可能感兴趣的:(KVO的使用以及原理)