KOV使用和原理探究

"通过runtime实现kov自定义,以及kov系统用法,了解kov原理"

Posted by gongkuihua on May 17, 2019

今天我来讲讲Ios常用的一个工具KVO

kov定义

键值观察Key-Value-Observer就是观察者模式。

观察者模式的定义:一个目标对象管理所有依赖于它的观察者对象,并在它自身的状态改变时主动通知观察者对象。这个主动通知通常是通过调用各观察者对象所提供的接口方法来实现的。观察者模式较完美地将目标对象与观察者对象解耦。 当需要检测其他类的属性值变化,但又不想被观察的类知道,有点像FBI监视嫌疑人,这个时候就可以使用KVO了。

  • 系统基本用法

    定义一个User对象,里面有一个userNmae的属性,现在来监听userName属性变化,下面是代码

    /*参数 *_user 观察那个对象 *self 谁来观察 *NSStringFromSelector(@selector(userName)) 观察对象的那个属性,是个字符串,防止写错字符串,不被提示,我这样写的,写@“userName”也是可以的,记,这个名字一定不能错,错了就不能监听了 options 这个是监听接受设置 是个枚举NSKeyValueObservingOptionNew 直接收新变化的数据 NSKeyValueObservingOptionOld 接受发通知的上个值 NSKeyValueObservingOptionInitial 注册时发一次通知,改变也会发通知 NSKeyValueObservingOptionPrior 改变之前发一次,改变之后发一次 */ [_user addObserver:self forKeyPath:NSStringFromSelector(@selector(userName)) options:NSKeyValueObservingOptionPrior context:nil];

观察者实现接受值的变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"%@",change);
} 观察者在自己消失之前告诉观察,我不需要观察了,你不用给我发消息了,因为我已不再了

- (void)dealloc{
    [self removeObserver:self forKeyPath:NSStringFromSelector(@selector(userName))];
} 最后实现调用查看结果

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    static int a;
    _user.userName = [NSString stringWithFormat:@"%d",a++];
} 我点击屏幕获得的结果 ![](https://upload-images.jianshu.io/upload_images/3889208-497f0acb7922cd12.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - 手动调用kvo -  有时候我不想收到监听结果,这个时候怎么办呢,苹果提供了属性监听设置方法 我们在user对象里面去设置下面这个方法,如果将自动监听返回No的话,是不能在监听数据变化了

//是否允许自动监听属性变化 默认开启
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
    return NO;
} 那如何让其又生效呢,我们可以手动调用监听,具体实现

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    static int a;
    //开始调用kvo
    [_user willChangeValueForKey:NSStringFromSelector(@selector(userName))];
    _user.userName = [NSString stringWithFormat:@"%d",a++];
    
    //结束调用kvo
    [_user didChangeValueForKey:NSStringFromSelector(@selector(userName))];
} 这种手动的应用场景在哪呢,例如:

//如果说只有值不相等时才发送通知,提升性能
- (void)setName:(NSString *)name
{
    if (![name isEqualToString:_name])
    {
 
        [self willChangeValueForKey:@"name"];
 
        _name = name;
 
        [self didChangeValueForKey:@"name"];
    }
} - 做属性依赖 - 这样kvo又继续实现了,下面我们又有一个需求,就是如果在user对象里面还有一个对象我们怎么监听他的成员变量变化呢,我们首先看看使用上面的方法可以不。

 [_user addObserver:self forKeyPath:@"dog.dogName" options:NSKeyValueObservingOptionNew context:nil];
 
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    static int a;
    //开始调用kvo
    [_user willChangeValueForKey:NSStringFromSelector(@selector(userName))];
//    _user.userName = [NSString stringWithFormat:@"%d",a++];
    _user.dog.dogName = [NSString stringWithFormat:@"%d",a++];
    //结束调用kvo
    [_user didChangeValueForKey:NSStringFromSelector(@selector(userName))];
} 这种是可以的,如果dog里面有很多,属性,我都想观察,那我都这么写嘛,好像不是很好,于是我们用一个属性关联来做这个事


+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
    NSLog(@"%@",key);
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
    if ([key isEqualToString:@"dog"]) {
        keyPaths = [[NSSet alloc] initWithObjects:@"_dog.dogName",@"_dog.dogage",@"_dog.dogtype", nil];
    }
    return keyPaths;
}
  • 自定义kvo

    直接上代码,自定义一个子类实现发通知和注册方法

    • (void)Gh_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{ //1.创建一个类 NSString *oldClassName = NSStringFromClass(self.class); NSString *newClassName = [@”GHKVO” stringByAppendingString:oldClassName]; Class MyClass = objc_allocateClassPair(self.class, newClassName.UTF8String, 0);

      //注册类 objc_registerClassPair(MyClass);

      //2.重写setName方法 /*MyClass 给那个类添加方法 *@selector(setName:) 方法标示 *(IMP)setName 方法地址 *“v@:@” 返回值类型,v是void :表示一个selector(sel)方法编号 @表示一个id类型的对象 就是参数

      */ class_addMethod(MyClass, @selector(setName:), (IMP)setName,”v@:@”);

      //3.修改isa指针 object_setClass(self, MyClass);

      //4将观察者保存当前对象 objc_setAssociatedObject(self, @”observer”, observer, OBJC_ASSOCIATION_ASSIGN); objc_setAssociatedObject(self, @”context”, CFBridgingRelease(context), OBJC_ASSOCIATION_ASSIGN); }

    void setName(id self,SEL _cmd,NSString *newName){ //调用父类的setName方法 Class class = [self class]; object_setClass(self, class_getSuperclass(class));//将子类变为父类 objc_msgSend(self,@selector(setName:),newName);//调用父类

    1
    2
    3
    4
    5
    6
    7
    8
    
      id observer = objc_getAssociatedObject(self, @"observer");
      id context = objc_getAssociatedObject(self, @"context");
      if (observer) {
          objc_msgSend(observer, @selector(GH_observeValueForKeyPath:ofObject:change:context:),@"name",self,@{@"new":newName,@"kind":@1},context);
      }
      //改回子类
      object_setClass(self, class);
      NSLog(@"来了");   } 调用
    
    [_user Gh_addObserver:self forKeyPath:@”name” options:NSKeyValueObservingOptionNew context:nil];
    • (void)GH_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ NSLog(@”%@”,change); } 实现结果
  • 对容器进行监听

    对容器进行监听我们使用

[_user addObserver:self forKeyPath:@”arry” options:NSKeyValueObservingOptionNew context:nil]; NSMutableArray *temp = [_user mutableArrayValueForKey:@”arry”];
1
2
3
4
5
6
[temp addObject:@"123123"];   结果   ![](https://upload-images.jianshu.io/upload_images/3889208-fe3901b712409c9b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 其中的kind 值表示
   	
NSKeyValueChangeSetting = 1,
NSKeyValueChangeInsertion = 2,//容器类插入
NSKeyValueChangeRemoval = 3,//容器里删除
NSKeyValueChangeReplacement = 4,//容器替换  欢迎一起沟通iOS学习技术 dmeo下载地址[demo](https://github.com/g1174117917/KVO_Sel)