KVO-基本使用方法-底层原理探究-自定义KVO-对容器类的监听

书读百变,其义自见!

将KVO形式以代码实现呈现,通俗易懂,更容易掌握 :GitHub   -链接如果失效请自动搜索:https://github.com/henusjj/KVO_base

代码中有详细的注释

一、KVO-常用方法

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

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

//移除
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

//监听模式(手动,自动),默认是自动Yes
+(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;


//属性的依赖,返回监听属性类的集合

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

二、KVO-基本使用

KVO监听属性值变化,从而做业务逻辑处理,监听属性变化,我们需要实现三步走

1.注册监听对象

2.实现监听方法

3.移除监听对象,避免crash

//
//  ViewController.m
//  KVO-基本用法
//
//  Created by GuoYanjun on 2019/1/9.
//  Copyright © 2019年 shiyujin. All rights reserved.
//

#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@property(nonatomic,strong)Person *p;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    _p=[[Person alloc]init];
//    注册
    [_p addObserver:self forKeyPath:NSStringFromSelector(@selector(name)) options:NSKeyValueObservingOptionNew context:nil];

}

//监听方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"%@",change);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
//    自动模式
    static int a =0;
    _p.name = [NSString stringWithFormat:@"%d",a++];
    
//    手动
//    [_p willChangeValueForKey:@"name"];
//    _p.name = [NSString stringWithFormat:@"%d",a++];
//    [_p didChangeValueForKey:@"name"];
//
    
}
-(void)dealloc{
    [_p removeObserver:self forKeyPath:@"name"];
}
@end

三、底层原理探究

这里我总结三个地方

1.创建一个子类,名字是:NSKVONotifying_Person  ,person是本项目中的类

  这里为什么是子类不是分类呢,这里说明以下,如果使用分类 ,他会覆盖set方法,导致原set方法中的逻辑处理失效

2.重写了set方法

  这里的重写不是重写父类的的set,而是重写子类的

3.外界改变isa指针

  此处可以在注册方法打一个断点,观察其isa指针的变化

    self->_p->isa:Person
     改变为
     self->_p->isa:
     NSKVONotifying_Person

   

四、对容器的监听

对于数组,我们添加元素的时候,都是addObject........

但是我们知道,KVO是针对set方法从而监听的,因为,

addObject........是不会响应的,此时,苹果给我提供了

//    [_p.arry addObject:[NSString stringWithFormat:@"%d",a++]];//这一步不会触发监听方法因为监听是监听set的方法,addObject不是set方法
    
    //解决方法
    NSMutableArray *tenp =[_p mutableArrayValueForKey:@"arry"];
    [tenp addObject:[NSString stringWithFormat:@"%d",a++]];

五、自定义KVO

此处需要用到Runtime,KVO文档中,我们会发现,相关方法是在NSobjet的分类,所以

1.创建一个NSObject的分类,定义注册方法

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSObject (JK_KVO)
- (void)JK_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
@end

NS_ASSUME_NONNULL_END

2.实现该方法

#import "NSObject+JK_KVO.h"
#import <objc/message.h>

@implementation NSObject (JK_KVO)

- (void)JK_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
    
//   1.创建一个类 -- self.class 就是Person
    NSString *oldname = NSStringFromClass(self.class);
    NSString *newNem = [@"JKKVO_" stringByAppendingString:oldname];
   Class myclass = objc_allocateClassPair(self.class, newNem.UTF8String, 0);
    // 注册类
    objc_registerClassPair(myclass);
        
//    2.重写子类set方法 -- 所谓的重写就是给子类添加f这个方法 setName,因为子类没有父类的setName方法!!!
    /* class :给那个类添加方法
     *sel:方法编号
     *imp :方法实现(函数指针)
     *type :返回值类型
     */
    class_addMethod(myclass, @selector(setName:), (IMP)setName, "v@:@");
    
//    3.修改isa指针
    object_setClass(self, myclass);
    
//    4.将观察保存到当前对象
    objc_setAssociatedObject(self, @"observer", observer, OBJC_ASSOCIATION_ASSIGN);
    
}

void setName(id self,SEL _cmd,NSString *newName){
    NSLog(@"来了--%@",newName);
//    调用父类的setName方法
    Class class =[self class];
    object_setClass(self, class_getSuperclass(class));//改成父类
    
    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

3.调用

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

----!!!!!!!!

OK,结束。代码已经整理完毕。下班

原文地址:https://www.cnblogs.com/henusyj-1314/p/10245363.html