iOS KVO

作者: coder_my | 来源:发表于2019-10-11 21:56 被阅读0次

    KVO全称Key-Value Observing,键值监听。

    基本使用和原理:

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
        NSLog(@"%@", change);
    }
    
    - (void)KVO {
        Person *p0 = [[Person alloc] init];
        Person *p1 = [[Person alloc] init];
        
        NSLog(@"before addObserver");
        
        NSLog(@"%@-%p", object_getClass(p0), object_getClass(p0));//Person
        NSLog(@"%@-%p", object_getClass(p1), object_getClass(p1));//Person
        NSLog(@"%d", object_getClass(p0) == object_getClass(p1));//1
        
        [p0 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
        
        NSLog(@"after addObserver");
        
        NSLog(@"%@-%p", object_getClass(p0), object_getClass(p0));//NSKVONotifying_Person
        NSLog(@"%@-%p", object_getClass(p1), object_getClass(p1));//Person
        NSLog(@"%d", object_getClass(p0) == object_getClass(p1));//0
        
        p0.name = @"Jack";
    }
    

    KVO的本质:

    当我们给对象注册一个观察者添加了KVO监听时,系统会修改这个对象的isa指针指向。在运行时,动态创建一个新的子类,NSKVONotifying_A类,将A的isa指针指向这个子类,来重写原来类的set方法;set方法实现内部会顺序调用willChangeValueForKey方法、原来的setter方法实现、didChangeValueForKey方法,而didChangeValueForKey方法内部又会调用监听器的observeValueForKeyPath:ofObject:change:context:监听方法。

    手动触发KVO
    实现调用willChangeValueForKey和didChangeValueForKey方法。

        //手动触发
        [p0 willChangeValueForKey:@"name"];
        [p0 didChangeValueForKey:@"name"];
    

    自定义KVO

    Person.h

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface Person : NSObject
    
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, copy) NSString *gender;
    
    - (void)custom_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
    
    @end
    
    NS_ASSUME_NONNULL_END
    

    Person.m

    #import "Person.h"
    
    #import <objc/runtime.h>
    #import <objc/message.h>
    
    static const char *custom_observer = "custom_observer";
    static const char *custom_getter = "custom_getter";
    static const char *custom_setter = "custom_setter";
    
    @implementation Person
    
    void setMethod(id self, SEL _cmd, id newValue) {
        
        //getter
        NSString *getterName = objc_getAssociatedObject(self, custom_getter);
        
        //setter
        NSString *setterName = objc_getAssociatedObject(self, custom_setter);
        
        Class class = [self class];
        
        //isa指向原类
        object_setClass(self, class_getSuperclass(class));
        
        //获取旧值
        id oldValue = objc_msgSend(self, NSSelectorFromString(getterName));
        
        //调用原类set方法
        objc_msgSend(self, NSSelectorFromString([setterName stringByAppendingString:@":"]), newValue);
        
        id observer = objc_getAssociatedObject(self, custom_observer);
        
        NSMutableDictionary *change = [NSMutableDictionary dictionary];
        if (newValue)
            change[NSKeyValueChangeNewKey] = newValue;
        if (oldValue)
            change[NSKeyValueChangeOldKey] = oldValue;
        
        objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:), getterName, self, change, nil);
        
        object_setClass(self, class);
    }
    
    - (void)custom_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context {
        
        //创建指定前缀CustomKVONotifying_%@的子类并注册
        NSString *curClassName = NSStringFromClass([self class]);
        NSString *customClassName = [NSString stringWithFormat:@"CustomKVONotifying_%@", curClassName];
        
        Class customClass = objc_getClass(customClassName.UTF8String);
        if (!customClass) {
            customClass = objc_allocateClassPair([self class], customClassName.UTF8String, 0);
            objc_registerClassPair(customClass);
        }
        
        //set方法首字母大写
        NSString *keyPathChange = [[[keyPath substringToIndex:1] uppercaseString] stringByAppendingString:[keyPath substringFromIndex:1]];
        NSString *setNameStr = [NSString stringWithFormat:@"set%@", keyPathChange];
        SEL setSEL = NSSelectorFromString([setNameStr stringByAppendingString:@":"]);
        
        //添加set方法
        Method getMethod = class_getInstanceMethod([self class], @selector(keyPath));
        const char *types = method_getTypeEncoding(getMethod);
        class_addMethod(customClass, setSEL, (IMP)setMethod, types);
        
        //修改isa
        object_setClass(self, customClass);
        
        //保存observer
        objc_setAssociatedObject(self, custom_observer, observer, OBJC_ASSOCIATION_ASSIGN);
        
        //保存set、get方法名
        objc_setAssociatedObject(self, custom_setter, setNameStr, OBJC_ASSOCIATION_COPY_NONATOMIC);
        objc_setAssociatedObject(self, custom_getter, keyPath, OBJC_ASSOCIATION_COPY_NONATOMIC);
        
    }
    
    @end
    

    Demo地址:KVO

    相关文章

      网友评论

          本文标题:iOS KVO

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