美文网首页
KVO 的使用

KVO 的使用

作者: heart_领 | 来源:发表于2018-09-21 16:44 被阅读5次

一、 简介
KVO的全称是Key-Value Observing, 翻译过来就是键值监听,可以用于监听某个对象属性值的改变
二、API
相关的API都在NSKeyValueObserving.h这个头文件里
三、常用的API

  1. 添加观察者:addObserver:forKeyPath:options:context:
  2. 实现观察相应方法:observeValueForKeyPath:ofObject:change:context:
  3. 在观察者释放之前移除对象上的监听:removeObserver:forKeyPath:
    四、关联
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    _p = [Person new];
    /**
[_p addObserver:self forKeyPath:@"steps" options:NSKeyValueObservingOptionPrior | NSKeyValueObservingOptionNew context:nil];
     添加观察者后动态生成了Person的子类NSKVONotifying_Person,并动态添加了四个方法
     (
     "setSteps:",//(Foundation`_NSSetIntValueAndNotify)方法实现为Foundation框架中的_NSSetIntValueAndNotify方法
     class,//(Foundation`NSKVOClass)方法实现为Foundation框架中的NSKVOClass方法
     dealloc,//(Foundation`NSKVODeallocate)方法实现为Foundation框架中的NSKVODeallocate方法
     "_isKVOA"//(Foundation`NSKVOIsAutonotifying)方法实现为Foundation框架中的NSKVOIsAutonotifying方法
     )
     */
///添加观察者
    [_p addObserver:self forKeyPath:@"fullName" options:NSKeyValueObservingOptionPrior | 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 {
    _p.firstName = @"123456";
//    [_p willChangeValueForKey:@"firstName"];//手动开启
//    _p.firstName = @"123456";//关闭自动通知观察者,要想观察到,每次都要调用这对方法
//    [_p didChangeValueForKey:@"firstName"];

}
- (void) dealloc {
    [_p removeObserver:self forKeyPath:@"fullName"];//移除后isa又指回了父类,生成的子类依然存在
}
@implementation Person
- (NSString*)fullName {
    return [NSString stringWithFormat:@"%@ %@", _firstName, _lastName];
}
//关联, lastName 和 firstName任意一个改变都会观察到fullName
+ (NSSet*) keyPathsForValuesAffectingFullName
{
    return [NSSet setWithObjects:@"lastName", @"firstName", nil];
}
//关闭自动通知观察者,不手动开启无法通知观察者
+ (BOOL) automaticallyNotifiesObserversOfFirstName {
    return NO;
}

/** 过程
 NSKeyValueWillChange
 [TZPerson setSteps:]
 NSKeyValueDidChange
 NSKeyValueNotifyObserver
 - (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
 */
//下断点:watchpoint set variable self->_p->_steps
//- (void) setSteps:(int)steps {
//    _steps = steps;
//}
@end

/// 打印对应的类及子类
- (void) printClasses:(Class) cls {
    /// 注册类的总数
    int count = objc_getClassList(NULL, 0);
    /// 创建一个数组, 其中包含给定对象
    NSMutableArray* array = [NSMutableArray arrayWithObject:cls];
    /// 获取所有已注册的类
    Class* classes = (Class*)malloc(sizeof(Class)*count);//sizeof(Class)*count:所有注册类的大小
    objc_getClassList(classes, count);
    
    /// 遍历s,class_getSuperclass(classes[i]):获取每一个类的父类
    for (int i = 0; i < count; i++) {
        if (cls == class_getSuperclass(classes[i])) {
            [array addObject:classes[i]];
        }
    }
    free(classes);
    NSLog(@"classes = %@", array);
}

五、自定义KVO

@interface NSObject (KVO) 

// 添加观察者
- (void)gv_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

// 删除观察者
- (void)gv_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

@end

#import <objc/message.h>

@implementation NSObject (KVO)
/// 添加观察者
- (void)gv_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context {
    // 动态创建一个子类
    Class newClass = [self createClass:keyPath];
    // 修改了isa的指向,指向动态创建的子类
    object_setClass(self, newClass);
    // 关联方法
    objc_setAssociatedObject(self, (__bridge void *)@"objc", observer, OBJC_ASSOCIATION_ASSIGN);
}

// NSKVONotifying_TZPerson
- (Class) createClass:(NSString*) keyPath {
    // 1. 拼接子类名 // Person
    NSString* oldName = NSStringFromClass([self class]);
    NSString* newName = [NSString stringWithFormat:@"TZKVONotifying_%@", oldName];
    // 2. 创建并注册类
    Class newClass = NSClassFromString(newName);
    if (!newClass) {
        // 创建并注册类
        newClass = objc_allocateClassPair([self class], newName.UTF8String, 0);
        objc_registerClassPair(newClass);
        // 添加一些方法
        // class
        Method classMethod = class_getInstanceMethod([self class], @selector(class));
        const char* classTypes = method_getTypeEncoding(classMethod);
        class_addMethod(newClass, @selector(class), (IMP)tz_class, classTypes);
        
        // setter
        NSString* setterMethodName = setterForGetter(keyPath);
        SEL setterSEL = NSSelectorFromString(setterMethodName);
        Method setterMethod = class_getInstanceMethod([self class], setterSEL);
        const char* setterTypes = method_getTypeEncoding(setterMethod);
        class_addMethod(newClass, setterSEL, (IMP)tz_setter, setterTypes);
        // 添加析构方法
        SEL deallocSEL = NSSelectorFromString(@"dealloc");
        Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
        const char* deallocTypes = method_getTypeEncoding(deallocMethod);
        class_addMethod(newClass, deallocSEL, (IMP)myDealloc, deallocTypes);
    }
    return newClass;
}

void myDealloc(id self, SEL _cmd) {
    // 父类
    Class superClass = [self class];//class_getSuperclass(object_getClass(self));
//    指回父类
    object_setClass(self, superClass);
    
    NSLog(@"");
}

#pragma mark - c 函数,ru
static void tz_setter(id self, SEL _cmd, id newValue) {
    NSLog(@"%s", __func__);
    /**
     self:Person
     _cmd:"setName:"
     newValue:Tom
     */
    struct objc_super superStruct = {
        self,
        class_getSuperclass(object_getClass(self))
    };
   
    // 改变父类的值
    objc_msgSendSuper(&superStruct, _cmd, newValue);
    
    // 通知观察者, 值发生改变了
    // 观察者
    id observer = objc_getAssociatedObject(self, (__bridge void *)@"objc");
    NSString* setterName = NSStringFromSelector(_cmd);
    NSString* key = getterForSetter(setterName);
    
    objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:), key, self, @{key:newValue}, nil);
}

//销毁执行该方法
Class tz_class(id self, SEL _cmd) {
    return class_getSuperclass(object_getClass(self));
}
/// 移除观察者
//- (void)gv_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
//
//    // 父类
//    Class superClass = [self class];//class_getSuperclass(object_getClass(self));
////    指回父类
//    object_setClass(self, superClass);
//
//}

#pragma mark - 从get方法获取set方法的名称 key ===>>> setKey:
static NSString  * setterForGetter(NSString *getter){
    
    if (getter.length <= 0) { return nil; }
    
//    NSString *firstString = [[getter substringToIndex:1] uppercaseString];
//    NSString *leaveString = [getter substringFromIndex:1];
//    return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
    NSString *method = [getter capitalizedString];
    return [NSString stringWithFormat:@"set%@:",method];
    
}

#pragma mark - 从set方法获取getter方法的名称 set<Key>:===> Key
static NSString * getterForSetter(NSString *setter){
    
    if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) { return nil;}
    
    NSRange range = NSMakeRange(3, setter.length-4);
    NSString *getter = [setter substringWithRange:range];
    NSString *firstString = [[getter substringToIndex:1] lowercaseString];
    getter = [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
    
    return getter;
}
@end

添加Blcok

typedef void(^TZKVOBlock)(id observer, NSString* keyPath, id oldValue, id newValue);

@interface NSObject (KVO)
- (void) tz_addObserverBlock:(NSObject*) observer forKeyPath:(NSString*) keyPath handle:(TZKVOBlock) handleBlock;

@end
#import <objc/message.h>

static const char* kTZKVOAssiociateKey = "kTZKVOAssiociateKey";

@interface TZInfo : NSObject

@property (nonatomic, weak) NSObject* observer;
@property (nonatomic, strong) NSString* keyPath;
@property (nonatomic, copy) TZKVOBlock hanleBlock;

@end
@implementation TZInfo

- (instancetype) initWithObserver:(NSObject*)observer forKeyPath:(NSString*) keyPath handleBlock:(TZKVOBlock) block {
    if (self == [super init]) {
        _observer = observer;
        _keyPath = keyPath;
        _hanleBlock = block;
    }
    return self;
}

@end
@implementation NSObject (KVO)
- (void) tz_addObserverBlock:(NSObject*) observer forKeyPath:(NSString*) keyPath handle:(TZKVOBlock) handleBlock {
    // 动态创建一个子类
    Class newClass = [self createClass:keyPath];
    // 修改了isa的指向
    object_setClass(self, newClass);
    
    // 信息保存
    TZInfo* info = [[TZInfo alloc] initWithObserver:observer forKeyPath:keyPath handleBlock:handleBlock];
    NSMutableArray* array = objc_getAssociatedObject(self, kTZKVOAssiociateKey);
    if (!array) {
        array = [NSMutableArray array];
        objc_setAssociatedObject(self, kTZKVOAssiociateKey, array, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    [array addObject:info];
}

// NSKVONotifying_TZPerson
- (Class) createClass:(NSString*) keyPath {
   
    // 1. 拼接子类名 // Person
    NSString* oldName = NSStringFromClass([self class]);
    NSString* newName = [NSString stringWithFormat:@"TZKVONotifying_%@", oldName];
    
    // 2. 创建并注册类
    Class newClass = NSClassFromString(newName);
    if (!newClass) {
        
        // 创建并注册类
        newClass = objc_allocateClassPair([self class], newName.UTF8String, 0);
        objc_registerClassPair(newClass);
        
        // 添加一些方法
        // class
        Method classMethod = class_getInstanceMethod([self class], @selector(class));
        const char* classTypes = method_getTypeEncoding(classMethod);
        class_addMethod(newClass, @selector(class), (IMP)tz_class, classTypes);
        
        // setter
        NSString* setterMethodName = setterForGetter(keyPath);
        SEL setterSEL = NSSelectorFromString(setterMethodName);
        Method setterMethod = class_getInstanceMethod([self class], setterSEL);
        const char* setterTypes = method_getTypeEncoding(setterMethod);
        
        class_addMethod(newClass, setterSEL, (IMP)tz_setter, setterTypes);
        
        // 添加析构方法
        SEL deallocSEL = NSSelectorFromString(@"dealloc");
        Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
        const char* deallocTypes = method_getTypeEncoding(deallocMethod);
        class_addMethod(newClass, deallocSEL, (IMP)myDealloc, deallocTypes);
    }
    return newClass;
}

void myDealloc(id self, SEL _cmd) {
    // 父类
    Class superClass = [self class];//class_getSuperclass(object_getClass(self));
    
    object_setClass(self, superClass);
    
    NSLog(@"");
}

#pragma mark - c 函数
static void tz_setter(id self, SEL _cmd, id newValue) {
    NSLog(@"%s", __func__);
    
    struct objc_super superStruct = {
        self,
        class_getSuperclass(object_getClass(self))
    };
    
    // keypath
       NSString* keyPath = getterForSetter(NSStringFromSelector(_cmd));
    
    // 获取旧值
    // kVC
    id oldValue = objc_msgSendSuper(&superStruct, NSSelectorFromString(keyPath));
    
    // 改变父类的值
    objc_msgSendSuper(&superStruct, _cmd, newValue);
    
    NSMutableArray* array = objc_getAssociatedObject(self, kTZKVOAssiociateKey);
    if (array) {
        for (TZInfo* info in array) {
            if ([info.keyPath isEqualToString:keyPath]) {
                info.hanleBlock(info.observer, keyPath, oldValue, newValue);
                return;
            }
        }
    }
}


Class tz_class(id self, SEL _cmd) {
    return class_getSuperclass(object_getClass(self));
}
#pragma mark - 从get方法获取set方法的名称 key ===>>> setKey:
static NSString  * setterForGetter(NSString *getter){
    
    if (getter.length <= 0) { return nil; }
    
    NSString *firstString = [[getter substringToIndex:1] uppercaseString];
    NSString *leaveString = [getter substringFromIndex:1];
    
    return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}

#pragma mark - 从set方法获取getter方法的名称 set<Key>:===> Key
static NSString * getterForSetter(NSString *setter){
    
    if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) { return nil;}
   
    NSRange range = NSMakeRange(3, setter.length-4);
    NSString *getter = [setter substringWithRange:range];
    NSString *firstString = [[getter substringToIndex:1] lowercaseString];
    getter = [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
    
    return getter;
}
@end

相关文章

  • KVO基本使用

    分三部分解释KVO一.KVO基本使用二.KVO原理解析三.自定义实现KVO 一、KVO基本使用 使用KVO,能够非...

  • KVO

    目录 1. KVO的使用1.1 KVO基本使用方法1.2 KVO手动触发模式1.3 KVO属性依赖1.4 KVO容...

  • iOS原理篇(一): KVO实现原理

    KVO实现原理 什么是 KVO KVO 基本使用 KVO 的本质 总结 一 、 什么是KVO KVO(Key-Va...

  • 如何优雅地使用 KVO

    如何优雅地使用 KVO 如何优雅地使用 KVO

  • KVO 原理探究

    [TOC] KVO 研究 没有使用KVO和使用KVO的变化 测试的类Person 通过 objc_copyClas...

  • [iOS] KVO的指导

    nshipster - KVO 如何优雅地使用 KVO

  • iOS-KVO

    一.kvo使用 kvo可以监听一个对象属性的变化,下面为简单使用. 二.使用runtime分析kvo 我写了个简单...

  • KVO如何才能直接监听到数组的变化

    转自关于使用KVO监听数组的问题 首先,数组不能直接使用KVO使用监听。当我们想要使用KVO监听数组的状态时改变然...

  • 20.iOS底层学习之KVO 原理

    本篇提纲1、KVO简介;2、KVO的使用;3、KVO的一些细节;4、KVO的底层原理; KVO简介 KVO全称Ke...

  • iOS 开发Tip2

    51.封装KVO 使用(注意:对UITextfiled使用在真机上不可以使用KVO,KVO是基于KVC,因为通过键...

网友评论

      本文标题:KVO 的使用

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