KVO

目录

  • 1. KVO的使用
    • 1.1 KVO基本使用方法
    • 1.2 KVO手动触发模式
    • 1.3 KVO属性依赖
    • 1.4 KVO容器类监听
  • 2. KVO底层实现

1. KVO的使用

1.1 KVO基本使用方法
  • 被监听对象调用-addObserver:forKeyPath:options:context:方法,传入监听者、被监听对象的属性(或keyPath)
  • 监听者实现-observeValueForKeyPath:ofObject:change:context:方法,获得监听回调
  • 最后记得在-dealloc方法里移除监听 -removeObserver:forKeyPath:

实例:假设有类Person,你需要监听它的name变化情况

//Person.h
@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
@end
//Person.m
@implementation Person
@end
//ViewController.h
@interface ViewController : UIViewController
@property (strong, nonatomic) Person *person;
@end

//ViewController.m
@implementation MyViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //添加监听代码
    self.person = [Person new];
    [self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
}

//获得回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    NSLog(@"%@", change);
}

- (void)dealloc {
    //移除监听
    [self.person removeObserver:self forKeyPath:@"name"];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    self.person.name = @"herui";
}

@end

1.2 KVO手动触发模式
  • 被监听类实现+automaticallyNotifiesObserversForKey:方法,返回NO,标识为手动触发模式
  • 当被监听对象属性的set方法被调用时不再触发监听回调,而是需要调用-willChangeValueForKey:以及-didChangeValueForKey:方法方能触发监听回调
//Person.h
@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
@end
//Person.m
@implementation Person
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    return NO;
}
@end
//ViewController.h
@interface ViewController : UIViewController
@property (strong, nonatomic) Person *person;
@end

//ViewController.m
@implementation MyViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //添加监听代码
    self.person = [Person new];
    [self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
}

//实现监听回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    NSLog(@"%@", change);
}

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

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.person willChangeValueForKey:@"name"];
//    self.person.name = @"herui";
    [self.person didChangeValueForKey:@"name"];
}

@end

1.3 KVO属性依赖
  • 被监听类实现+keyPathsForValuesAffectingValueForKey:方法并返回需要被依赖属性的set集合

实例:假设Person类有一个属性Dog,而Dog又有属性nick、level,你需要监听这两个属性的变化情况。有两种方式可以做到:

  • 多写几次addObserverremoveObserver
  • KVO属性依赖;

假设Dog有很多个需要被监听的属性呢?你准备写多少次?

//Dog.h
@interface Dog : NSObject
@property (copy, nonatomic) NSString *nick;
@property (assign, nonatomic) int level;
@end
//Dog.m
@implementation Dog
@end

//Person.h
@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
@property (strong, nonatomic) Dog *dog;
@end
//Person.m
@implementation Person
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
    if([key isEqualToString:@"dog"]){
        return [[NSSet alloc] initWithObjects:@"_dog.nick", @"_dog.level", nil];
    }
    return [super keyPathsForValuesAffectingValueForKey:key];
}
@end
//ViewController.h
@interface ViewController : UIViewController
@property (strong, nonatomic) Person *person;
@end

//ViewController.m
@implementation MyViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //添加监听代码
    self.person = [Person new];
    self.person.dog = [Dog new];

    [self.person addObserver:self forKeyPath:@"dog" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
}

//实现监听回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    NSLog(@"%@", change);
}

- (void)dealloc {
    //移除监听
    [self.person removeObserver:self forKeyPath:@"dog"];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    self.person.dog.nick = @"小白";
    self.person.dog.level = 1;
}

@end

1.4 KVO容器类监听
  • 不能直接拿被监听容器对象进行操作,而是要通过-mutableArrayValueForKey: -mutableSetValueForKey:方法来获取到被监听的容器对象,再对它进行操作才能触发监听回调

可以监听到数组、集合中的元素变化情况

@interface MyViewController ()
@property (strong, nonatomic) NSMutableArray *array;

@end

@implementation MyViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.array = [NSMutableArray array];
    
    [self addObserver:self forKeyPath:@"array" options:(NSKeyValueObservingOptionNew) context:nil];
}

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

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    //重点在这里
    NSMutableArray *arrM = [self mutableArrayValueForKey:@"array"];
    [arrM addObject:@1];
}

@end
2. KVO底层实现
  • 创建子类(代码无污染)
  • 重写setter方法
  • 修改isa指针
//NSObject+KVO.h
@interface NSObject (KVO)
- (void)addObserver:(NSObject *)observer forKey:(NSString *)key;
@end

//NSObject+KVO.m
#import 
#import 

@implementation NSObject (KVO)

- (SEL)setterSELForKey:(NSString *)key {
    return NSSelectorFromString([NSString stringWithFormat:@"set%@:", [key capitalizedString]]);
}

- (void)addObserver:(NSObject *)observer forKey:(NSString *)key {
    //创建并注册子类
    NSString *className = [@"KVO_" stringByAppendingString:NSStringFromClass(self.class)];
    Class cls = objc_allocateClassPair(self.class, className.UTF8String, 0);
    objc_registerClassPair(cls);

    //重写setter方法(添加)
    //name: 方法名 SEL
    //imp: 方法实现 IMP(即函数地址)
    //types: 参数 返回值 的类型编码
    class_addMethod(cls, [self setterSELForKey:key], (IMP)setter, "v@:@");

    //修改isa
    object_setClass(self, cls);

    //保存参数
    objc_setAssociatedObject(self, "observer", observer, OBJC_ASSOCIATION_ASSIGN);
    objc_setAssociatedObject(self, "key", key, OBJC_ASSOCIATION_ASSIGN);
}

void setter(id self, SEL _cmd, id value)
{
    id key = objc_getAssociatedObject(self, "key");
    id observer = objc_getAssociatedObject(self, "observer");

    //调用父类方法(改isa指针后,直接调用)
    Class cls = [self class];
    object_setClass(self, class_getSuperclass(cls));
    objc_msgSend(self, [self setterSELForKey:key], value);

    //通知监听者
    if([observer respondsToSelector:@selector(observeValueForKeyPath:ofObject:change:context:)]){
        objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:), key, self, @{@"new":value}, nil);
    }

    //恢复isa指针
    object_setClass(self, cls);
}

@end

你可能感兴趣的:(KVO)