kvo

235 阅读2分钟

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

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

@interface TGPerson : NSObject

@property (nonatomic,assign) NSInteger age;

@end

@implementation TGPerson

@end

@interface ViewController ()

@property (nonatomic,strong) TGPerson* person;
@property (nonatomic,strong) TGPerson* person2;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.person = [[TGPerson alloc] init];
    self.person.age = 10;
    
    self.person2 = [[TGPerson alloc] init];
    self.person2.age = 10;
    
    //打印结果  监听前 TGPerson,TGPerson
//    NSLog(@" 监听前 %@,%@",object_getClass(self.person),object_getClass(self.person2));
    
    NSLog(@"添加KVO监听之前 - %p %p",
                    [self.person methodForSelector:@selector(setAge:)],
                    [self.person2 methodForSelector:@selector(setAge:)]);
    
    NSKeyValueObservingOptions option = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew;
    [self.person addObserver:self forKeyPath:@"age" options:option context:@"123"];
     /**
      打印结果  监听后 NSKVONotifying_TGPerson,TGPerson
      1、person1的类对象发生变化,KVO本质利用运行时的动态特性 生成一个继承TGPerson的子类
2、修改isa指针,person1对象的isa指向这个类
3、重写setter方法
      */
//     NSLog(@" 监听后 %@,%@",object_getClass(self.person),object_getClass(self.person2));
    
    /**
     打印结果  监听后 setAge:方法变成了 (Foundation`_NSSetLongLongValueAndNotify)
     由此可见 _NSSetLongLongValueAndNotify方法 重写了Setter方法
     _NSSetLongLongValueAndNotify内部实现伪代码
    1、 [self.person willChangeValueForKey:@"age"]; (可以TGPerson对象实现系统做法)
    2、 //原来Setter方法
    3、 [self.person didChangeValueForKey:@"age"];
    4、didChangeValueForKey:内部会调用observer的observeValueForKeyPath:ofObject:change:context:方法
     */
    NSLog(@"添加KVO监听之后 - %p %p",
          [self.person methodForSelector:@selector(setAge:)],
          [self.person2 methodForSelector:@selector(setAge:)]);
    
    /**
     打印结果:  setAge:,class,dealloc,_isKVOA,
        NSKVONotifying_TGPerson 内部还实现了,class,dealloc,_isKVOA
     class :// 屏幕内部实现,隐藏了NSKVONotifying_TGPerson类的存在
     dealloc: //销毁操作
     */
    [self printMethodNameOfClass:object_getClass(self.person)];
    /**
     打印结果: setAge:,age,
     */
    [self printMethodNameOfClass:object_getClass(self.person2)];
}


- (void)printMethodNameOfClass:(Class)clazz {
    
    NSMutableString *nameStr = [NSMutableString string];
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(clazz, &count);
    for (int i = 0; i<count; i++) {
       Method method = methodList[I];
       NSString *methodName = NSStringFromSelector( method_getName(method));
       [nameStr appendString:methodName];
       [nameStr appendString:@","];
    }
    free(methodList);
    NSLog(@"类%@  %@",clazz,nameStr);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
   
    self.person.age = 20;
}

/**
 当监听对象的属性值发生改变时,就会调用
 @param keyPath 被监听的属性名称
 @param object  被监听的对象
 @param change 值得变化
 @param context 注册监听的时候传入的值
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
     //获取新值  [object valueForKeyPath:keyPath];
    NSLog(@"%@对象%@属性 值%@发生了变化 ---- 新值%@ ",object,keyPath,change,[object valueForKeyPath:keyPath]);
}

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

@end

未使用KVO对象内部结构.png

使用KVO对象内部结构.png

1116822-e47e79aedb0dee61.png

1116822-4b651db3de6de365.png

NSKVONotifying_TGPerson 伪代码
#import "NSKVONotifying_MJPerson.h"

@implementation NSKVONotifying_MJPerson

- (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];
}

// 屏蔽内部实现,隐藏了NSKVONotifying_MJPerson类的存在
- (Class)class
{
    return [MJPerson class];
}

- (void)dealloc
{
    // 收尾工作
}

- (BOOL)_isKVOA
{
    return YES;
}