iOS高级进阶之KVO

作者: 平安喜乐698 | 来源:发表于2018-10-24 19:59 被阅读2次
    目录
        1. 原理
        2. 使用
        3. 自己实现KVO
        4. 第三方框架KVOController
    
    KVO(key Value Observing) 键值观察
        一个对象监听另一个对象指定属性的变化。
    
        KVO和NSNotificationCenter同属于观察者模式。
        KVO对监听对象无侵入性(不修改对象内部代码)。
    
    系统自带KVO的缺点:
        1、很容易导致崩溃(见下面的例子中的说明)。
        2、keyPath是字符串,不进行合法性检查 且 修改属性明后要全局搜索并修改。
        3、不支持block语法,多个被观察者时需要在observeValueForKeyPath做很多判断
    
    1. KVO的原理
    在运行时会创建一个中间类(继承原类),并动态修改当前对象的isa指向中间类(即动态修改了对象的类型),重写class方法(返回原类的Class,是其更像没有变动过一样)。
    

    分析原理

    第一步:创建自定义类
    
    @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<NSKeyValueChangeKey,id> *)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. 使用
        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<NSKeyValueChangeKey,id> *)change context:(void *)context{
    
            //NSKeyValueChangeNewKey  NSKeyValueChangeOldKey
        }
    
    
    第三步:不需要监听时移除观察者
        // 需要在观察者消失前即观察者不能为nil,否则崩溃。因为KVO未对观察者强引用
        // 必须和addObserver成对出现,忘记remove则属性改变后会崩溃
        // 多次remove会导致崩溃
        // delloc中移除观察者
        [personM removeObserver:self forKeyPath:@"name"];
    

    手动调用

    默认 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;
    }
    
    1. 自己实现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 <Foundation/Foundation.h>
    #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 <objc/runtime.h>
    #import <objc/message.h>
    
    
    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<KVOObserverItem *> *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 <KVOObserverItem *>* 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 <KVOObserverItem *>* 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 <Foundation/Foundation.h>
    
    // 属性发生变化后回调
    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
    
    1. 第三方框架KVOController
    pod 'KVOController'
    
    #import <KVOController/KVOController.h>
    
    
        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<NSString *,id> * _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<NSString *,id> * _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

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