美文网首页
iOS KVO机制的基本使用及实现原理

iOS KVO机制的基本使用及实现原理

作者: Fly0_0 | 来源:发表于2019-01-20 23:10 被阅读0次

我用#CSDN#《iOS KVO机制的基本使用及实现原理》 https://blog.csdn.net/u011212411/article/details/86562942

在Cocoa Touch框架中,观察者模式的具体应用有两个:通知(Notification)机制和KVO(Key-Value-Observing)机制。

KVO不同于通知机制那样通过一个NSNotificationCenter通知所有观察者对象,是在对象属性发生变化时通知会被直接发送给观察者对象,也可以手动模式,没有改变仍可调用

一、KVO基本使用

使用KVO分三个步骤:

1、通过addObserver方法注册观察者,观察者可以接受keyPath属性的变化事件。

- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

2、在观察者中实现observeValueForKeyPath方法,当keyPath属性发生改变后,KVO会回调这个方法来通知观察者。

- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;

3、当观察者不需要监听时,可以调用removeObserver方法将KVO移除。需要注意调用removeObserver需要在观察者消失之前,否则会导致Crash。

- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

addObserver方法

observer参数:要被观察的对象

keyPath参数:需要观察的属性。由于是字符串形式,传错容易导致Crash。一版利用系统的反射机制NSStringFromSelector(@selector(keyPath))

options参数:为属性变化设置的选项,NSKeyValueObservingOptions是一个枚举类型

NSKeyValueObservingOptionNew      接受新值,默认

NSKeyValueObservingOptionOld      接受旧值

NSKeyValueObservingOptionInitial    在注册时立即接收一次回调,在改变时也会发送通知

NSKeyValueObservingOptionPrior    改变之前发一次,改变之后发一次

content参数:上下文内容,它的类型是C语言形式的任何指针类型

*注意:在调用addObserver方法后,KVO并不会对观察者进行强引用,所以需要注意观察者的生命周期,否则会导致观察者被释放带来的Crash。

监听回调

观察者需要实现observeValueForKeyPath:ofObject:change:context:方法,如果没有实现会导致Crash。

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '<ViewController: 0x7fab9ad0f150>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.

keyPath参数:监听属性名称

object参数:被观察对象

change参数:字典类型,包含了属性变化的内容,根据options时传入的枚举来返回

context参数:注册时传递的上下文内容

实例代码

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, strong) NSString *name;

@property (nonatomic, strong) NSString *age;

@property (nonatomic, strong) NSString *sex;

@property (nonatomic, strong) NSMutableArray *arr;

@end

@implementation ViewController

- (void)viewDidLoad {

    [super viewDidLoad];

    self.name = @"name";

    self.age = @"age";

    self.sex = @"sex";

    self.arr = [[NSMutableArray alloc] init];

   

    //添加观察者

    //监听一个属性

    [self addObserver:self forKeyPath:NSStringFromSelector(@selector(sex)) options:NSKeyValueObservingOptionNew context:nil];

    //监听多个属性

    [self addObserver:self forKeyPath:@"self" options:NSKeyValueObservingOptionNew context:nil];

    //监听一个容器

    [self addObserver:self forKeyPath:NSStringFromSelector(@selector(arr)) options:NSKeyValueObservingOptionNew context:nil];

}

//监听方法

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context

{

    NSLog(@"\nkeyPath:%@\nobject:%@\nchange:%@\ncontext:%@",keyPath,object,change,context);

    NSLog(@"属性:%@ %@ %@",self.name,self.age,self.sex);

}

/**

触发模式 自动还是手动

手动模式

-willChangeValueForKey

-didChangeValueForKey

@param key 关注的属性

@return 是否自动d调用

*/

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {

    if ([key isEqualToString:@"sex"]) {

        return NO;

    }

    return YES;

}

/**

KVO本身会自动观察同一个实例的所有密钥路径,并在任何密钥路径的值发生变化时向观察者发送密钥的更改通知。

@param key 建路径

@return 返回一组键值 属性的键路径

*/

+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key

{

    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];

    if ([key isEqualToString:@"self"]) {

        keyPaths = [[NSSet alloc] initWithObjects:@"name",@"age", nil];

    }

    return keyPaths;

}

//点击

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

{

    static int num;

    //自动触发

    self.name = [NSString stringWithFormat:@"name:%d",num++];

    self.age = [NSString stringWithFormat:@"age:%d",num];

    //KVO方法 mutableArrayValueForKey

    NSMutableArray *temparr = [self mutableArrayValueForKey:@"arr"];

    [temparr addObject:[NSString stringWithFormat:@"arr:%d",num]];

   

    //手动触发

    [self willChangeValueForKey:@"sex"];

    self.sex = [NSString stringWithFormat:@"sex:%d",num%2];

    [self didChangeValueForKey:@"sex"];

}

//移除KVO

- (void)dealloc

{

    [self removeObserver:self forKeyPath:@"self"];

    [self removeObserver:self forKeyPath:@"sex"];

    [self removeObserver:self forKeyPath:@"arr"];

}

@end

输出

2019-01-20 18:00:03.937949+0800 KVO使用[10696:320443]

keyPath:self

object:<ViewController: 0x7ffa5ac1bb90>

change:{

    kind = 1;

    new = "<ViewController: 0x7ffa5ac1bb90>";

}

context:(null)

2019-01-20 18:00:03.938252+0800 KVO使用[10696:320443] 属性:name:0 (null) (null)

2019-01-20 18:00:03.938467+0800 KVO使用[10696:320443]

keyPath:self

object:<ViewController: 0x7ffa5ac1bb90>

change:{

    kind = 1;

    new = "<ViewController: 0x7ffa5ac1bb90>";

}

context:(null)

2019-01-20 18:00:03.938586+0800 KVO使用[10696:320443] 属性:name:0 age:1 (null)

2019-01-20 18:00:03.938868+0800 KVO使用[10696:320443]

keyPath:sex

object:<ViewController: 0x7ffa5ac1bb90>

change:{

    kind = 1;

    new = "sex:0";

}

context:(null)

2019-01-20 18:00:03.939170+0800 KVO使用[10696:320443] 属性:name:0 age:1 sex:0

二、KVO底层实现

KVO的实现依赖于Runtime,当监听某个对象的某个属性时,有几点步骤

1、创建一个子类 NSKVONotyfing_对象名,该类继承自目标对象的本类

2、重写setName方法

3、外界改变isa指针

#import "NSObject+FLYKVO.h"

#import <objc/message.h>

@implementation NSObject (FLYKVO)

- (void)FLY_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context

{

    //1、创建一个类

    NSString *oldClassName = NSStringFromClass(self.class);

    NSString *newClassName = [@"FLYKVO_" stringByAppendingString:oldClassName];

    Class MyClass = objc_allocateClassPair(self.class, newClassName.UTF8String, 0);

    //注册类

    objc_registerClassPair(MyClass);

   

    /**

    2、重写setName方法

    @param MyClass 给类添加方法

    @param setName: 方法编码

    @param IMP 方法实现 函数指针

    @param type 函数返回类型 v=void @=object :=selector @=object

    */

    class_addMethod(MyClass, @selector(setName:), (IMP)setName, "v@:@");

   

    //3、修改isa指针 指向子类

    object_setClass(self, MyClass);

   

    //4、将观察者保存到当前对象  不要用strong 要用weak 否则循环引a用

    objc_setAssociatedObject(self, @"observer", observer, OBJC_ASSOCIATION_ASSIGN);

}

void setName(id self, SEL _cmd, NSString *newName) {

    NSLog(@"来了老弟!!!");

   

    Class class = [self class];

    //改成父类

    object_setClass(self, class_getSuperclass(class));

    //调用父类的setName方法

    objc_msgSend(self, @selector(setName:),newName);

   

    //观察者

    id observer = objc_getAssociatedObject(self, @"observer");

    if (observer) {

        objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),@"name",self,@{@"new:":newName,@"kind":@1},nil);

    }

   

    //改回子类

    object_setClass(self, class);

}

@end

调用自定义的监听方法

[self FLY_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];

相关文章

  • iOS KVO机制的基本使用及实现原理

    我用#CSDN#《iOS KVO机制的基本使用及实现原理》 https://blog.csdn.net/u0112...

  • iOS底层学习文章

    iOS黑魔法-Method Swizzling Objective-C 反射机制 KVC原理剖析 KVO原理分析及...

  • KVO和KVC的使用及原理解析

    一 KVO基本使用 二 KVO本质原理讲解及代码验证 三 KVC基本使用 四 KVC设值原理 五 KVC取值原理 ...

  • KVO底层原理分析

    一、 KVO内部实现原理 KVO是基于 runtime机制实现的,使用了isa 混写(isa-swizzling)...

  • iOS 进阶原理知识笔记

    KVO实现原理 KVO基本原理: 1 kvo是基于runtime机制实现的 2 当某个类的属性对象第一次被观察时,...

  • iOS 进阶原理知识随笔

    KVO实现原理 KVO基本原理: 1 kvo是基于runtime机制实现的 2 当某个类的属性对象第一次被观察时,...

  • iOS 进阶原理知识笔记

    KVO实现原理 KVO基本原理: kvo是基于runtime机制实现的 当某个类的属性对象第一次被观察时,系统就会...

  • ios开发进阶基础知识

    KVO实现原理 KVO基本原理: kvo是基于runtime机制实现的 当某个类的属性对象第一次被观察时,系统就会...

  • ios原理分析

    KVO实现原理 KVO基本原理: kvo是基于runtime机制实现的 当某个类的属性对象第一次被观察时,系统就会...

  • iOS-KVO(二) 使用注意点

    iOS-KVO(一) 基本操作iOS-KVO(二) 使用注意点iOS-KVO(三) 窥探底层实现iOS-KVO(四...

网友评论

      本文标题:iOS KVO机制的基本使用及实现原理

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