KVO实现原理

KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变。

下面来探讨一下KVO的本质

1.新建一个XZPerson类

#import <Foundation/Foundation.h>

@interface XZPerson : NSObject

 @property(nonatomic,assign)int age;

@end


#import "XZPerson.h"

@implementation XZPerson

- (void)setAge:(int)age{

    _age = age;

}

@end


#import "ViewController.h"
#import "XZPerson.h"
#import <objc/runtime.h>

@interface ViewController ()
@property(nonatomic,strong)XZPerson *person1;
@property(nonatomic,strong)XZPerson *person2;
@end

@implementation ViewController
-(void)printMethodNameOFclass:(Class)class{
    unsigned int count;
    //获取方法数组
    Method *methodList = class_copyMethodList(class, &count);
    //存储方法名
    NSMutableString*methodNames = [NSMutableString string];
    for (int i = 0; i<count; i++) {
        //获取方法
        Method meth = methodList[i];
        // 获取方法名
        NSString * selname =NSStringFromSelector(method_getName(meth));
        [methodNames appendFormat:@"%@,",selname];
    }
    free(methodList);
    NSLog(@"%@-%@",class,methodNames);
}
- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.person1 = [[XZPerson alloc] init];
    self.person1.age = 10;
    
    self.person2 = [[XZPerson alloc] init];
    self.person2.age = 10;
    NSLog(@"person1添加kvo之前-%@-%p-%p",object_getClass(self.person1),[self.person1 methodForSelector:@selector(setAge:)],
          [self.person2 methodForSelector:@selector(setAge:)]);
    //获取方法实现IMP
    [self.person1 methodForSelector:@selector(setAge:)];
    [self.person2 methodForSelector:@selector(setAge:)];
    
    //给person对象添加观察者
    [self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
//    //获取方法实现IMP
    [self.person1 methodForSelector:@selector(setAge:)];
    [self.person2 methodForSelector:@selector(setAge:)];
    
    NSLog(@"person1添加kvo之后-%@-%p-%p",object_getClass(self.person1),[self.person1 methodForSelector:@selector(setAge:)],
          [self.person2 methodForSelector:@selector(setAge:)]);
    NSLog(@"添加kvo后的person的父类%@",[object_getClass(self.person1) superclass]);
    ///NSKVONotifying_XZPerson
    //p IMP(0x100773f8e)
    //[self.person1 setAge:100];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.person1.age = 20;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"%@的属性%@的值由%@变成了%@",object,keyPath,change[@"old"],change[@"new"]);
}

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

控制台打印结果如下:

2018-10-10 10:27:39.808750+0800 KVOTEST[1067:45659] person1添加kvo之前-XZPerson-0x1022fe4b0-0x1022fe4b0

2018-10-10 10:27:39.809313+0800 KVOTEST[1067:45659] person1添加kvo之后-NSKVONotifying_XZPerson-0x1026a3f8e-0x1022fe4b0

2018-10-10 10:27:39.809650+0800 KVOTEST[1067:45659] 添加kvo后的person的父类XZPerson

(lldb) p IMP(0x1022fe4b0)

(IMP) $0 = 0x00000001022fe4b0 (KVOTEST`-[XZPerson setAge:] at XZPerson.m:14)

(lldb) p IMP(0x1026a3f8e)

(IMP) $1 = 0x00000001026a3f8e (Foundation`_NSSetIntValueAndNotify)

(lldb) 

由此可以看出:person1 在添加kvo监听之后,生成了一个  NSKVONotifying_XZPerson的类,其父类为XZPerson;

使用lldb查看person1的setAge方法的实现,发现添加KVO之后的person1在setAge方法内部调用了 Foundation 的`_NSSetIntValueAndNotify方法,这里涉及到Foundation框架的反编译。此方法的伪代码如下:

- (void)setAge:(int)age {

_NSSetIntValueAndNotify();

}

// 伪代码

void _NSSetIntValueAndNotify()

{

[self willChangeValueForKey:@"age"];

[super setAge:age];

[self didChangeValueForKey:@"age"];

}

- (void)didChangeValueForKey:(NSString *)key

{ // 通知监听器,某某属性值发生了改变

[oberser observeValueForKeyPath:key ofObject:self change:nil context:nil];

总结:

  • iOS用什么方式实现对一个对象的KVO(KVO的本质是什么?)
  • 利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
  • 当修改instance对象的属性时,会调用Foundation_NSSetXXXValueAndNotify函数
  1. willChangeValueForKey:
  2. 父类原来的setter
  3. didChangeValueForKey:
  •  内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:

2.如何手动触发KVO?

手动调用willChangeValueForKey:didChangeValueForKey:方法

猜你喜欢

转载自blog.csdn.net/qq_33726122/article/details/82992808