iOS基础之KVO(Key Value Observing) 键值观察

目录
    1. KVO的原理
    2. 系统自带KVO的缺点
    3. 自己实现KVO
    4. 第三方框架KVOController

可用来监听某一对象指定属性的变化
KVO和NSNotificationCenter同属于观察者模式

1. KVO的原理

KVO对监听对象不具有侵入性(不会修改监听对象内部代码),因为KVO的原理是:
在运行时会创建一个继承自原类的中间类,并动态修改监听对象的isa指向该中间类(动态修改了对象的类型,重写class方法返回原类的Class,是其更像没有变动过一样),而该中间类覆写了指定属性的set方法,在赋值前调用willChangeValueForKey,在赋值后调用didChangeValueForKey。这样一来,当监听对象的指定属性发生变化时,观察者会收到相应通知并调用observeValueForKeyPath方法。

分析原理

第一步:创建自定义类

@interface PersonM : NSObject
@property (nonatomic,copy) NSString *name;
@end

@implementation PersonM
- (NSString *)description {
    NSLog(@"object address : %p \n", self);
    
    IMP nameIMP = class_getMethodImplementation(object_getClass(self), @selector(setName:));
    NSLog(@"object setName: IMP %p object", nameIMP);
    
    Class objectMethodClass = [self class];
    Class objectRuntimeClass = object_getClass(self);
    Class superClass = class_getSuperclass(objectRuntimeClass);
    NSLog(@"objectMethodClass : %@, ObjectRuntimeClass : %@, superClass : %@ \n", objectMethodClass, objectRuntimeClass, superClass);
    
    NSLog(@"object method list \n");
    unsigned int count;
    Method *methodList = class_copyMethodList(objectRuntimeClass, &count);
    for (NSInteger i = 0; i < count; i++) {
        Method method = methodList[i];
        NSString *methodName = NSStringFromSelector(method_getName(method));
        NSLog(@"method Name = %@\n", methodName);
    }
    
    return @"";
}
@end


第二步:VC中+
    PersonM *personM=[PersonM new];
    PersonM *personM2=[PersonM new];
    [personM description];
    [personM2 description];
    [personM addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
    [personM description];
    [personM2 description];
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    
        NSLog(@"%@",change);
    }



第三步:查看打印信息
  未被观察前打印
  object address : 0x6000026ba740
  object setName: IMP 0x10fad55f0 object
  objectMethodClass : PersonM, ObjectRuntimeClass : PersonM, superClass : NSObject
  object method list
  method Name = .cxx_destruct
  method Name = description
  method Name = name
  method Name = setName:

  object address : 0x6000026ba6e0
  object setName: IMP 0x10fad55f0 object
  objectMethodClass : PersonM, ObjectRuntimeClass : PersonM, superClass : NSObject
  object method list
  method Name = .cxx_destruct
  method Name = description
  method Name = name
  method Name = setName:

  被观察后打印
  object address : 0x6000026ba740
  object setName: IMP 0x10fef590a object
  objectMethodClass : PersonM, ObjectRuntimeClass : NSKVONotifying_PersonM, superClass : PersonM
  object method list
  method Name = setName:
  method Name = class
  method Name = dealloc
  method Name = _isKVOA

  object address : 0x6000026ba6e0
  object setName: IMP 0x10fad55f0 object
  objectMethodClass : PersonM, ObjectRuntimeClass : PersonM, superClass : NSObject
  object method list
  method Name = .cxx_destruct
  method Name = description
  method Name = name
  method Name = setName:

从打印中可以看出:
  KVO在运行时会根据原类创建一个中间类(继承原类,NSKVONotifying_PersonM类型)
  动态修改当前对象的isa指向中间类
  对KVO属性name覆写setName方法(调用willChangeValueForKey->改变值->didChangeValueForKey),未KVO则不覆写。
  _isKVOA标识KVO

1. 系统自带KVO的缺点

    1、很容易导致崩溃(见下面的例子中的说明)。
    2、keyPath是字符串,不会进行合法性检查;修改属性名后要全局搜索并修改。
    3、不支持block语法,多个被观察者时需要在observeValueForKeyPath做很多判断

使用

    PersonModel *personM=[PersonModel new];

第一步:注册观察者
    // 给personM添加观察者self,观察name属性的变化
    [personM addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
    /*
     options :会观察到并传入observeValueForKeyPath方法
        NSKeyValueObservingOptionOld     旧值
       NSKeyValueObservingOptionNew     新值(默认仅接受新值)
       NSKeyValueObservingOptionInitial 注册观察者后立即调用一次回调
       NSKeyValueObservingOptionPrior   分2次调用(在值改变之前和值改变之后)

      context:任意对象,observeValueForKeyPath方法中可获取
     */


第二步:在观察者中实现observeValueForKeyPath,没有实现则属性改变后会崩溃
    // 观察方法(当所观察的东西发生变化时调用)
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{

        //NSKeyValueChangeNewKey  NSKeyValueChangeOldKey
    }


第三步:不需要监听时移除观察者
    // 需要在观察者消失前即观察者不能为nil,否则崩溃。因为KVO未对观察者强引用
    // 必须和addObserver成对出现,忘记remove则属性改变后会崩溃
    // 多次remove会导致崩溃
    // delloc中移除观察者,如果不移除当监听对象指定属性发生变化时会发生崩溃。
    [personM removeObserver:self forKeyPath:@"name"];

手动调用observeValueForKeyPath

覆写 set方法、kvc设值 后会自动调用observeValueForKeyPath

// 手动调用
-(void)setName:(NSString *)name{
    if(![_name isEqualToString: name]){
        [self willChangeValueForKey:@"name"];
        _name=name;
        [self didChangeValueForKey:@"name"];
    }
}
// true则自动调用,false则手动调用
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
    BOOL automatic = NO;
    if ([theKey isEqualToString:@"name"]) {
        automatic = NO;
    } else {
        automatic = [super automaticallyNotifiesObserversForKey:theKey];
    }
    return automatic;
}

监听NSMutableArray、NSMutableSet、NSMutableSet属性

例如监听NSMutableArray类型的属性,正常进行addObject、removeObject操作是不会触发,

解决:
  // 使用KVC获取
  NSMutableArray *studentArr=[person mutableArrayValueForKeyPath:@"studentArr"];

    NSMutableSet *set=[person mutableSetValueForKeyPath:@""];
    NSMutableOrderedSet *orderSet=[person mutableOrderedSetValueForKeyPath:@""];
    NSMutableArray *arr=[person mutableArrayValueForKeyPath:@""];
    NSMutableSet *set=[person mutableSetValueForKey:@""];
    NSMutableOrderedSet *orderSet=[person mutableOrderedSetValueForKey:@""];
    NSMutableArray *arr=[person mutableArrayValueForKey:@""];
3. 自己实现KVO

注意:只用于了解原理,不能在项目中使用,尚存在许多问题。

1、创建NSObject+KVOBlock文件和KVOObserverItem文件
2、使用
    添加观察者
    [personM addObserver:self originalSelector:@selector(name) callback:^(id  _Nonnull observedObject, NSString * _Nonnull observedKey, id  _Nonnull oldValue, id  _Nonnull newValue) {
      
        NSLog(@"====================");
    }];
    移除观察者
    [personM removeObserver:self originalSelector:@selector(name)];

NSObject+KVOBlock.h

#import 
#import "KVOObserverItem.h"


@interface NSObject (KVOBlock)
/**
 添加观察者

 @param observer 观察者
 @param originalSelector 用于获取被观察者的属性
 @param callback 属性发生变化后的回调
 */
- (void)addObserver:(NSObject *)observer
       originalSelector:(SEL)originalSelector
               callback:(KVOObserverBlock)callback;

/**
 移除观察者

 @param observer  观察者
 @param originalSelector 用于获取被观察者的属性
 */
- (void)removeObserver:(NSObject *)observer
          originalSelector:(SEL)originalSelector;

@end

NSObject+KVOBlock.m

#import "NSObject+KVOBlock.h"
#import 
#import 


static void *const KVOObserverAssociatedKey = (void *)&KVOObserverAssociatedKey;
static NSString *KVOClassPrefix = @"my_KVONotifying_";

@implementation NSObject (KVOBlock)

- (void)addObserver:(NSObject *)observer
       originalSelector:(SEL)originalSelector
               callback:(KVOObserverBlock)callback {
    
    // 1.判断是否有这个key对应的selector,如果没有则Crash。在编译时异常及早发现,避免在运行时异常。
    SEL originalSetter = NSSelectorFromString(setterForGetter(originalSelector));
    Method originalMethod = class_getInstanceMethod(object_getClass(self), originalSetter);
    if (!originalMethod) {
        NSString *exceptionReason = [NSString stringWithFormat:@"%@ Class %@ setter SEL not found.", NSStringFromClass([self class]), NSStringFromSelector(originalSelector)];
        NSException *exception = [NSException exceptionWithName:@"NotExistKeyExceptionName" reason:exceptionReason userInfo:nil];
        [exception raise];
    }
    
    // 2.判断当前类是否是KVO子类,如果不是则创建,并设置其isa指针
    Class kvoClass = object_getClass(self);
    NSString *kvoClassString = NSStringFromClass(kvoClass);
    if (![kvoClassString hasPrefix:KVOClassPrefix]) {
        kvoClass = [self makeKVOClassWithName:kvoClassString];
        object_setClass(self, kvoClass);
    }
    
    // 3.如果没有实现setter方法则添加。
    if (![self hasMethodWithKey:originalSetter]) {
        class_addMethod(kvoClass, originalSetter, (IMP)kvoSetter, method_getTypeEncoding(originalMethod));
    }
    
    // 4.创建观察者对象,并添加到观察者数组中,并动态设置为属性。
    KVOObserverItem *observerItem = [[KVOObserverItem alloc] initWithObserver:observer key:NSStringFromSelector(originalSelector) block:callback];
    NSMutableArray *observers = objc_getAssociatedObject(self, KVOObserverAssociatedKey);
    if (observers == nil) {
        observers = [NSMutableArray array];
    }
    [observers addObject:observerItem];
    objc_setAssociatedObject(self, KVOObserverAssociatedKey, observers, OBJC_ASSOCIATION_RETAIN);
}

- (void)removeObserver:(NSObject *)observer
          originalSelector:(SEL)originalSelector {
    
    // 获取观察者数组。并移除指定观察者
    NSMutableArray * observers = objc_getAssociatedObject(self, KVOObserverAssociatedKey);
    [observers enumerateObjectsUsingBlock:^(KVOObserverItem * _Nonnull mapTable, NSUInteger idx, BOOL * _Nonnull stop) {
        SEL selector = NSSelectorFromString(mapTable.key);
        if (mapTable.observer == observer && selector == originalSelector) {
            [observers removeObject:mapTable];
        }
    }];
}

#pragma mark 私有Method、Funcation


/**
 重写set方法实现

 @param self <#self description#>
 @param selector <#selector description#>
 @param value <#value description#>
 */
static void kvoSetter(id self, SEL selector, id value) {
    // 1.
    id (*getterMsgSend) (id, SEL) = (void *)objc_msgSend;
    NSString *getterString = getterForSetter(selector);
    SEL getterSelector = NSSelectorFromString(getterString);
    id oldValue = getterMsgSend(self, getterSelector);
    
    // 2.创建super的结构体,并向super发送属性的消息
    id (*msgSendSuper) (void *, SEL, id) = (void *)objc_msgSendSuper;
    struct objc_super objcSuper = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self))
    };
    msgSendSuper(&objcSuper, selector, value);
    
    // 3.循环触发观察者的回调
    NSMutableArray * observers = objc_getAssociatedObject(self, KVOObserverAssociatedKey);
    [observers enumerateObjectsUsingBlock:^(KVOObserverItem * _Nonnull mapTable, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([mapTable.key isEqualToString:getterString] && mapTable.block) {
            mapTable.block(self, NSStringFromSelector(selector), oldValue, value);
        }
    }];
}


/**
 是否拥有指定方法

 @param key 方法SEL
 @return 是否拥有
 */
- (BOOL)hasMethodWithKey:(SEL)key {
    NSString *setterName = NSStringFromSelector(key);
    unsigned int count;
    Method *methodList = class_copyMethodList(object_getClass(self), &count);
    for (NSInteger i = 0; i < count; i++) {
        Method method = methodList[i];
        NSString *methodName = NSStringFromSelector(method_getName(method));
        if ([methodName isEqualToString:setterName]) {
            return YES;
        }
    }
    return NO;
}



/**
 用于获取属性名(setName->name)

 @param setter set方法SEL
 @return 属性名
 */
static NSString * getterForSetter(SEL setter) {
    NSString *setterString = NSStringFromSelector(setter);
    if (![setterString hasPrefix:@"set"]) {
        return nil;
    }
    
    NSString *getterString = [setterString substringWithRange:NSMakeRange(4, setterString.length - 5)];
    NSString *firstString = [setterString substringWithRange:NSMakeRange(3, 1)];
    firstString = [firstString lowercaseString];
    getterString = [NSString stringWithFormat:@"%@%@", firstString, getterString];
    return getterString;
}



/**
 用于获取set方法名(name->setName)

 @param getter 属性名
 @return set方法名
 */
static NSString * setterForGetter(SEL getter) {
    NSString *getterString = NSStringFromSelector(getter);
    NSString *firstString = [getterString substringToIndex:1];
    firstString = [firstString uppercaseString];
    
    NSString *setterString = [getterString substringFromIndex:1];
    setterString = [NSString stringWithFormat:@"set%@%@:", firstString, setterString];
    return setterString;
}


/**
 判断是否存在KVO类
    如果存在则返回;
    如果不存在,则创建继承自本类的中间类:KVO类,重写KVO类的class方法,指向本类的父类。

 @param name 原类名
 @return KVO类
 */
- (Class)makeKVOClassWithName:(NSString *)name {
    //
    NSString *className = [NSString stringWithFormat:@"%@%@", KVOClassPrefix, name];
    Class kvoClass = objc_getClass(className.UTF8String);
    if (kvoClass) {
        return kvoClass;
    }
    //
    kvoClass = objc_allocateClassPair(object_getClass(self), className.UTF8String, 0);
    objc_registerClassPair(kvoClass);
    //
    const char * types = NSStringFromSelector(@selector(class)).UTF8String;
    class_addMethod(kvoClass, @selector(class), (IMP)super_kvoClass, types);
    
    return kvoClass;
}


/**
 获取父类

 @param self <#self description#>
 @param selector <#selector description#>
 @return <#return value description#>
 */
static Class super_kvoClass(id self, SEL selector) {
    return class_getSuperclass(object_getClass(self));
}

@end

KVOObserverItem.h

#import 

// 属性发生变化后回调
typedef void (^KVOObserverBlock) (id observedObject, NSString *observedKey, id oldValue, id newValue);


@interface KVOObserverItem : NSObject

// 观察者
@property (nonatomic, weak) NSObject *observer;
// 观察者要监测的属性
@property (nonatomic, copy) NSString *key;
// 属性发生变化后回调
@property (nonatomic, copy) KVOObserverBlock block;

/**
 初始化观察者

 @param observer 观察者
 @param key 观察者要监测的属性
 @param block 属性发生变化后回调
 @return 实例
 */
- (instancetype)initWithObserver:(NSObject *)observer
                             key:(NSString *)key
                           block:(KVOObserverBlock)block;
@end

KVOObserverItem.m

#import "KVOObserverItem.h"

@implementation KVOObserverItem


- (instancetype)initWithObserver:(NSObject *)observer
                             key:(NSString *)key
                           block:(KVOObserverBlock)block {
    self = [super init];
    if (self) {
        self.observer = observer;
        self.key = key;
        self.block = block;
    }
    return self;
}

@end
4. 第三方框架KVOController
pod 'KVOController'
#import 


    PersonM *personM=[PersonM new];
    // 创建FBKVOController
    FBKVOController *kvo=[FBKVOController controllerWithObserver:self];
添加被观察者 属性
    // block
    [kvo observe:personM keyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary * _Nonnull change) {
        // change[NSKeyValueChangeNewKey]
        // change[NSKeyValueChangeOldKey]
    }];
    // selector
    [kvo observe:personM keyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld action:@selector(handleChangeName:)];
    // 一组值
    [kvo observe:personM keyPaths:@[@"name",@"age"] options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld  block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary * _Nonnull change) {
    }];
    [kvo observe:personM keyPaths:@[@"name",@"age"] options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld action:@selector(handleChange:)];
移除被观察者
    // 移除指定对象
    [kvo unobserve:personM];
    // 移除指定对象的指定属性
    [kvo unobserve:personM keyPath:@"name"];
    // 移除全部
    [kvo unobserveAll];

你可能感兴趣的:(iOS基础之KVO(Key Value Observing) 键值观察)