iOS KVO

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

基本使用和原理:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)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 

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 
#import 

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)