美文网首页
KVO的使用(一)

KVO的使用(一)

作者: dandelionYD | 来源:发表于2019-01-08 12:46 被阅读0次

上一篇我们看了KVO的本质,接下来我们来试试使用KVO吧


准备工作:

Person_.h
#import <Foundation/Foundation.h>
@interface Person_ : NSObject
@property (nonatomic,assign)int  age;
@end

Person_.m
#import "Person_.h"
@implementation Person_
@end

Student.h
#import <Foundation/Foundation.h>
@interface Student : NSObject
@property (nonatomic,strong)NSString  *name;
@end

Student.m
#import "Student.h"
@implementation Student
-(void)dealloc{
    NSLog(@"%s",__FUNCTION__);
}
@end

School.h
#import <Foundation/Foundation.h>
#import "Person_.h"
#import "Student.h"

@interface School : NSObject
@property (nonatomic,strong)  Person_ *leader;
@property (nonatomic,strong)NSMutableArray<Student*> *stuArr;
@property (nonatomic,assign)int  lastTime;
@end

School.m
#import "School.h"
@implementation School
@end

1.观察者的添加和移除观察者

KVOBaseUsesViewController_1.m
@interface KVOBaseUsesViewController_1 ()
@property (nonatomic,strong)School  *myhool;
@end


- (void)viewDidLoad {
    [super viewDidLoad];
    
    //初始化对象
    self.myhool = [[School alloc]init];
    Person_ *leader  = [[Person_ alloc]init];
    leader.age = 60;
    self.myhool.leader = leader;
    self.myhool.lastTime = 100;
    
    [self addObserver_1];
    
    [self addObserver_2];
    
    [self addObserver_3];
 }
 
 
 ----------------------------------------------------------------

//observer观察者对象,值变化时通知的对象

//keyPath监听的属性:是字符串,也可以使用对象属性的属性的字符串

/*
 context通知的上下文:
 1.当同一个观察者对同一类的不同对象同一属性
 2.OR不同类的对象的相同属性进行监听时,可以使用改参数进行区分
 */

/*
 NSKeyValueObservingOptions(可以写成“|”的形式哟,来获取前后的变值)
 1.NSKeyValueObservingOptionNew:属性更改的新值
 2.NSKeyValueObservingOptionOld:属性更改前的旧值
 3.NSKeyValueObservingOptionInitial:如果设置了这个值,将会立刻向观察者对象发送一次通知
 4.NSKeyValueObservingOptionPrior:设置了该值后会在属性发生改变前和改变后都通知一次
 */
-(void)addObserver_1{
    [self.myhool addObserver:self forKeyPath:@"lastTime" options:NSKeyValueObservingOptionNew context:@"lastTime"];
    [self.myhool addObserver:self forKeyPath:@"leader.age" options:NSKeyValueObservingOptionOld context:@"leader.age"];
}

-(void)addObserver_2{
    //只监听最基本的属性值的改变
    [self.myhool addObserver:self forKeyPath:@"lastTime" options:NSKeyValueObservingOptionInitial context:@"lastTime"];
    //监听了属性对象里面的属性值的改变
    [self.myhool addObserver:self forKeyPath:@"leader.age" options:NSKeyValueObservingOptionPrior context:@"leader.age"];
}

-(void)addObserver_3{
     //写成“|”的形式哟
     [self.myhool addObserver:self forKeyPath:@"lastTime" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:@"lastTime"];
}

//实现监听的方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
      NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}

//点击屏幕改变属性
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.myhool.lastTime = 120;
    self.myhool.leader.age = 180;
}

//移除观察者
-(void)dealloc{
    
    //全部移除:lastTime的监听
    [self.myhool removeObserver:self forKeyPath:@"lastTime"];

    //只移除:上下文为->leader.age的监听
    [self.myhool removeObserver:self forKeyPath:@"leader.age" context:@"leader.age"];
        
    NSLog(@"%s",__FUNCTION__);
}

注意点:

移除观察者的时候:
添加1次
 [self.myhool addObserver:self forKeyPath:@"leader.age" options:NSKeyValueObservingOptionOld context:@"leader.age"];
 
移除2次:
 [self.myhool removeObserver:self forKeyPath:@"leader.age" context:@"leader.age"];
    
 [self.myhool removeObserver:self forKeyPath:@"leader.age" context:@"leader.age"];
    
此时:会崩溃的哟,弹出exception   
-----------------------------------------------
- (void)viewDidLoad {
    [super viewDidLoad];
    //初始化对象
    self.myhool = [[School alloc]init];
    
    Student *stu = [[Student  alloc]init];
    [self.myhool addObserver:stu forKeyPath:@"lastTime" options:NSKeyValueObservingOptionNew context:nil];
   
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
         [self.myhool removeObserver:stu forKeyPath:@"lastTime"];
    });
}
stu测试观察者对象被释放了,再次发送消息,则会崩溃(所以要去移除)(即:add和remove需要成对出现)

2.观察者监听的方法

对于NSKeyValueChangeKey里面的详细说明,后面会详细说明
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
   NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}

3.手动发送通知

#import "KVOBaseUsesViewController_3.h"

@interface Boy : NSObject
@property (nonatomic,strong)NSString  *name;
@end

@implementation Boy
/*关闭自动通知 */
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    return ![key isEqualToString:@"name"];
}

/* 手动发送通知 */
- (void)setName:(NSString *)name{
    [self willChangeValueForKey:@"name"];
    _name = name;
    NSLog(@"在改变呢");
    [self didChangeValueForKey:@"name"];
    NSLog(@"已经改变了值");
}
@end


@interface KVOBaseUsesViewController_3 ()
@property (nonatomic,strong)Boy  *myBoy;
@end

@implementation KVOBaseUsesViewController_3

- (void)viewDidLoad {
    [super viewDidLoad];
    self.myBoy = [[Boy alloc]init];
    [self.myBoy addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:nil];
}


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

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.myBoy.name = @"Lucky";
}
-(void)dealloc{
    [self.myBoy removeObserver:self forKeyPath:@"name" ];
}
@end

4.监听集合属性(NSMutableArray、NSMutableSet和NSMutableOrderedSet)

可参考苹果api

准备:

@interface Girl : NSObject
@property (nonatomic,strong)NSMutableArray  *clothes;
@end

@implementation Girl
@end

我们先来写一个按照常规的去监听集合属性

@interface KVOBaseUsesViewController_4 ()
@property (nonatomic,strong)Girl  *girl;
@end

@implementation KVOBaseUsesViewController_4

- (void)viewDidLoad {
    [super viewDidLoad];
     self.girl = [[Girl alloc]init];
     self.girl.clothes  = @[@"010101",@"000",@"111"].mutableCopy;
    [self.girl addObserver:self forKeyPath:@"clothes" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld  context:nil]; 
}

点击改变属性:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self.girl.clothes addObject:@"222"];
}
//发现:根本不会触发内部发生的变化监听(属性内部的集合里面的值不会监听到)

那怎样实现监听呢?(我们从苹果api说明参考看到如下实现)

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSMutableArray *arrM = [self.girl mutableArrayValueForKey:@"clothes"];
    NSLog(@"原始arrM = %@",arrM);
    [arrM addObject:@"222"];
    NSLog(@"添加之后arrM = %@",arrM);
    [arrM insertObject:@"333" atIndex:0];
    NSLog(@"插入之后arrM = %@",arrM);
    [arrM removeObject:@"111"];
    NSLog(@"移除之后arrM = %@",arrM);
    [arrM replaceObjectAtIndex:0 withObject:@"444"];
    NSLog(@"替换之后arrM = %@",arrM);
    //数组的其他方法...
}

实现监听的方法:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
     NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
    
     if([keyPath isEqualToString:@"clothes"]) {
         
         /*NSKeyValueChangeKindKey:
          *NSKeyValueChange枚举相关的值:如下
          enum {
          NSKeyValueChangeSetting = 1, //设置一个新值。被监听的属性可以是一个对象,也可以是一对一关系的属性或一对多关系的属性。
          NSKeyValueChangeInsertion = 2,// 表示一个对象被插入到一对多关系的属性。
          NSKeyValueChangeRemoval = 3,// 表示一个对象被从一对多关系的属性中移除。
          NSKeyValueChangeReplacement = 4 // 表示一个对象在一对多的关系的属性中被替换
          };typedef NSUInteger NSKeyValueChange;
          */
         NSNumber *kindStr =  change[NSKeyValueChangeKindKey];
         NSLog(@"属性变化的类型:%@",kindStr);
         
 
         /*NSKeyValueChangeOldKey:
          *属性的旧值。当NSKeyValueChangeKindKey是 NSKeyValueChangeSetting,
          *且添加观察的方法设置了NSKeyValueObservingOptionOld时,我们能获取到属性的旧值。
          *如果NSKeyValueChangeKindKey是NSKeyValueChangeRemoval或者NSKeyValueChangeReplacement,
          *且指定了NSKeyValueObservingOptionOld时,则我们能获取到一个NSArray对象,包含被移除的对象或
          *被替换的对象
          */
         NSArray *oldArray = change[NSKeyValueChangeOldKey];
         NSLog(@"旧值:%@",oldArray);
         
         /*NSKeyValueChangeNewKey:
          *属性的新值。当NSKeyValueChangeKindKey是 NSKeyValueChangeSetting,
          *且添加观察的方法设置了NSKeyValueObservingOptionNew时,我们能获取到属性的新值。
          *如果NSKeyValueChangeKindKey是NSKeyValueChangeInsertion或者NSKeyValueChangeReplacement,
          *且指定了NSKeyValueObservingOptionNew时,则我们能获取到一个NSArray对象,包含被插入的对象或
          *用于替换其它对象的对象。
          */
        NSArray *newArray = change[NSKeyValueChangeNewKey];
         
         /*NSKeyValueChangeIndexesKey:
          *如果NSKeyValueChangeKindKey的值是NSKeyValueChangeInsertion、NSKeyValueChangeRemoval
          *或者NSKeyValueChangeReplacement,则这个key对应的值是一个NSIndexSet对象,
          *包含了被插入、移除或替换的对象的索引
          */
        NSIndexSet *indexes = change[NSKeyValueChangeIndexesKey];
        __block NSInteger i = 0;
        [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
            NSLog(@"下标 : %ld, 新值 : %@", idx, newArray[i]);
            i++;
        }];
    }
    
}

这里说明都有:具体的可参考github上Demo实现

最后我们移除观察者对象

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

5.不重复发送通知(当属性数据还是跟上一次的一样就不发送通知)

#import "KVOBaseUsesViewController_5.h"

@interface Test : NSObject
@property (nonatomic,strong)NSString  *name;
@end

@implementation Test
/*关闭自动通知 */
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    return ![key isEqualToString:@"name"];
}

/* 过滤 */
- (void)setName:(NSString *)name{
    if (![name isEqualToString:_name]){ //相同的话就不发送
        [self willChangeValueForKey:@"name"];
        _name = name;
        [self didChangeValueForKey:@"name"];
    }
}

@end

@interface KVOBaseUsesViewController_5 ()
@property (nonatomic,strong)Test  *test;
@end

@implementation KVOBaseUsesViewController_5
- (void)viewDidLoad {
    [super viewDidLoad];
    self.test = [[Test alloc]init];
    
    [self.test addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:nil];
}

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

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.test.name = @"Lucky";
}

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

友情链接:

相关文章

  • KVO基本使用

    分三部分解释KVO一.KVO基本使用二.KVO原理解析三.自定义实现KVO 一、KVO基本使用 使用KVO,能够非...

  • iOS原理篇(一): KVO实现原理

    KVO实现原理 什么是 KVO KVO 基本使用 KVO 的本质 总结 一 、 什么是KVO KVO(Key-Va...

  • KVO

    目录 1. KVO的使用1.1 KVO基本使用方法1.2 KVO手动触发模式1.3 KVO属性依赖1.4 KVO容...

  • iOS-KVO

    一.kvo使用 kvo可以监听一个对象属性的变化,下面为简单使用. 二.使用runtime分析kvo 我写了个简单...

  • 20.iOS底层学习之KVO 原理

    本篇提纲1、KVO简介;2、KVO的使用;3、KVO的一些细节;4、KVO的底层原理; KVO简介 KVO全称Ke...

  • 如何优雅地使用 KVO

    如何优雅地使用 KVO 如何优雅地使用 KVO

  • OC语法:KVO的底层实现

    一、KVO是什么二、怎么使用KVO三、KVO的底层实现四、KVO常见面试题 一、KVO是什么 KVO全称Key-V...

  • KVO 原理探究

    [TOC] KVO 研究 没有使用KVO和使用KVO的变化 测试的类Person 通过 objc_copyClas...

  • [iOS] KVO的指导

    nshipster - KVO 如何优雅地使用 KVO

  • KVC、KVO

    KVC、KVO探识(一)KVO和KVO的详细使用 KVC、KVO探识(二)KVC你不知道的东西 KVC、KVO探识...

网友评论

      本文标题:KVO的使用(一)

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