美文网首页
KVO的使用(二)

KVO的使用(二)

作者: dandelionYD | 来源:发表于2019-01-08 12:47 被阅读0次

    接上次:KVO的使用(一)

    1.一个属性改变,发送多个通知

    name属性改变了,主动发生通知给name和height的监听

    #import "KVOBaseUsesViewController_6.h"
    
    @interface Test_6 : NSObject
    @property (nonatomic,strong)NSString  *name;
    @property (nonatomic,assign)double  height;
    @end
    
    @implementation Test_6
    
    /*关闭自动通知 */
    + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
        return ![key isEqualToString:@"name"];
    }
    
    - (void)setName:(NSString *)name{
        [self willChangeValueForKey:@"name"];
        [self willChangeValueForKey:@"height"];
        _name = name;
        _height = _height++;
        [self didChangeValueForKey:@"name"];
        [self didChangeValueForKey:@"height"];
    }
    @end
    
    @interface KVOBaseUsesViewController_6 ()
    @property (nonatomic,strong)Test_6  *test;
    @end
    
    @implementation KVOBaseUsesViewController_6
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.test = [[Test_6 alloc]init];
        
        [self.test addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:nil];
        [self.test addObserver:self forKeyPath:@"height" options:(NSKeyValueObservingOptionNew) context:nil];
    }
    
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
    }
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        self.test.name = @"Lucky";
    }
    
    -(void)dealloc{
        [self.test removeObserver:self forKeyPath:@"name"];
        [self.test removeObserver:self forKeyPath:@"height"];
    }
    @end
    

    2.手动执行NSKeyValueChange

    #import "KVOBaseUsesViewController_7.h"
    
    @interface Girl_7 : NSObject
    @property (nonatomic,strong)NSMutableArray  *clothes;
    @end
    
    @implementation Girl_7
    //关闭自动通知
    + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
        return ![key isEqualToString:@"clothes"];
    }
    
    -(void)removeClothesAtIndexes:(NSIndexSet *)indexes{
        NSLog(@"准备发送通知");
        [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:@"clothes"];
        [_clothes  removeObjectsAtIndexes:indexes];
        [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:@"clothes"];
        NSLog(@"已经发送通知");
    }
    @end
    
    @interface KVOBaseUsesViewController_7 ()
    @property (nonatomic,strong)Girl_7  *girl;
    @end
    
    @implementation KVOBaseUsesViewController_7
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.girl = [[Girl_7 alloc]init];
        self.girl.clothes  = @[@"010101",@"000",@"111"].mutableCopy;
        [self.girl addObserver:self forKeyPath:@"clothes" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld  context:nil];
    }
    
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        [self makeChange_1];
    }
    
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
        NSArray *newArray = change[NSKeyValueChangeNewKey];
        NSIndexSet *indexes = change[NSKeyValueChangeIndexesKey];
        __block NSInteger i = 0;
        [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
            NSLog(@"下标 : %ld, 新值 : %@", idx, newArray[i]);
            i++;
        }];
    }
    
    -(void)makeChange_1{
        [self.girl removeClothesAtIndexes:[NSIndexSet indexSetWithIndex:0]];
        NSLog(@"结果:%@",self.girl.clothes);
    }
    
    -(void)dealloc{
        [self.girl removeObserver:self forKeyPath:@"clothes"];
    }
    @end
    

    3.属性依赖

    监听result的值,当x或者y的值改变了就会触发result的通知

    #import "KVOBaseUsesViewController_8.h"
    
    @interface Calculator : NSObject
    @property (nonatomic, assign) double x;
    @property (nonatomic, assign) double y;
    @property (nonatomic, readonly,assign) double result;
    @end
    
    @implementation Calculator
    -(double)result{
        return self.x + self.y;
    }
    
    //实现属性依赖的方法
    + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
        if ([key isEqualToString:@"result"]) {
            return [NSSet setWithObjects:@"x", @"y", nil];
        }else {
            return [super keyPathsForValuesAffectingValueForKey:key];
        }
    }
    @end
    
    @interface KVOBaseUsesViewController_8 ()
    @property (nonatomic,strong)Calculator  *calculator;
    @end
    
    @implementation KVOBaseUsesViewController_8
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.calculator = [Calculator new];
        
        [self.calculator addObserver:self forKeyPath:@"result" options:(NSKeyValueObservingOptionNew) context:nil];
    }
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        self.calculator.x = 10;
        NSLog(@"改变了x,结果%f",self.calculator.result);
        
        [NSThread sleepForTimeInterval:5];
        self.calculator.y = 20;
        NSLog(@"改变了y,结果%f",self.calculator.result);
        
    }
    
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
        
    }
    
    -(void)dealloc{
        [self.calculator removeObserver:self forKeyPath:@"result"];
    }
    @end
    

    4.获取监听对象的内部信息

    import "KVOBaseUsesViewController_9.h"
    #import "Person_.h"
    
    @interface KVOBaseUsesViewController_9 ()
    @property (nonatomic,strong)Person_*p;
    @end
    
    @implementation KVOBaseUsesViewController_9
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.p = [Person_ new];
        [self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
        id info = self.p.observationInfo;
        NSLog(@"%@", [info description]);
    }
    
    -(void)dealloc{
        [self.p removeObserver:self forKeyPath:@"age"];
    }
    @end
    

    5.重复添加监听

    #import "KVOBaseUsesViewController_10.h"
    #import "Person_.h"
    
    @interface KVOBaseUsesViewController_10()
    @property (nonatomic,strong)Person_*p;
    @end
    
    @implementation KVOBaseUsesViewController_10
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.p = [Person_ new];
        [self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
        [self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
    }
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        self.p.age = 10;
    }
    
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
         NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
    }
    
    -(void)dealloc{
        [self.p removeObserver:self forKeyPath:@"age"];
        [self.p removeObserver:self forKeyPath:@"age"];
        
        //添加了2次 就移除2次(多一次会炸)
        //[self.p removeObserver:self forKeyPath:@"age"];
    }
    @end
    

    6.防止过多移除监听对象_1

    (最简单、笨的方法,通过try-catch,根本上还是未解决)

    #import "KVOBaseUsesViewController_11.h"
    #import "Person_.h"
    
    @interface KVOBaseUsesViewController_11()
    @property (nonatomic,strong)Person_*p;
    @end
    
    @implementation KVOBaseUsesViewController_11
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.p = [Person_ new];
        [self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
        [self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
        id info = self.p.observationInfo;
        NSLog(@"%@", [info description]);
    }
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        self.p.age = 10;
    }
    
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
    }
    
    -(void)dealloc{
        [self.p removeObserver:self forKeyPath:@"age"];
        [self.p removeObserver:self forKeyPath:@"age"];
        
        //添加了2次 就移除2次(多一次会炸)
        @try {
            [self.p removeObserver:self forKeyPath:@"age"]; //断点依旧还是走这~~
        } @catch (NSException *exception) {
            NSLog(@"多删除一次");
        } @finally {
            
        }
    }
    @end
    

    7.防止过多移除监听对象_2

    按照6的思路,我们可以通过 runtime来拦截系统的方法,来实现try-catch

    NSObject+myKVO.h
    #import <Foundation/Foundation.h>
    @interface NSObject (myKVO)
    @end
    
    NSObject+myKVO.m
    #import "NSObject+myKVO.h"
    #import <objc/runtime.h>
    
    @implementation NSObject (myKVO)
    +(void)load{
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Class cls = [NSObject class];
            //交换系统的方法
            SEL originalSelector = @selector(removeObserver:forKeyPath:);
            SEL swizzledSelector = @selector(myRemoveObserver:forKeyPath:);
            Method originalMethod = class_getInstanceMethod(cls, originalSelector);
            Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
            BOOL didAddMethod = class_addMethod(cls,
                                                originalSelector,
                                                method_getImplementation(swizzledMethod),
                                                method_getTypeEncoding(swizzledMethod));
            if(didAddMethod){
                class_replaceMethod(cls,
                                    swizzledSelector,
                                    method_getImplementation(originalMethod),
                                    method_getTypeEncoding(originalMethod));
            }
            else{
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
        });
    }
    
    - (void)myRemoveObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
        @try {
            [self myRemoveObserver:observer forKeyPath:keyPath];
        } @catch (NSException *exception) {}
    }
    @end
    
    实现:
    #import "KVOBaseUsesViewController_12.h"
    #import "Person_.h"
    #import "NSObject+myKVO.h"
    
    @interface KVOBaseUsesViewController_12()
    @property (nonatomic,strong)Person_*p;
    @end
    
    @implementation KVOBaseUsesViewController_12
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.p = [Person_ new];
        [self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
        [self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
        id info = self.p.observationInfo;
        NSLog(@"%@", [info description]);
    }
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        self.p.age = 10;
    }
    
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
    }
    
    -(void)dealloc{
        [self.p removeObserver:self forKeyPath:@"age"];
        [self.p removeObserver:self forKeyPath:@"age"];
        [self.p removeObserver:self forKeyPath:@"age"];
    }
    @end
    

    8.防止过多移除/添加监听对象_3

    我们可以自己写一个来进行添加和删除的控制(替换系统的方法)

    NSObject+myKVO2.h
    #import <Foundation/Foundation.h>
    @interface NSObject (myKVO2)
    @end
    
    NSObject+myKVO2.m
    #import "NSObject+myKVO2.h"
    #import <objc/runtime.h>
    
    @implementation NSObject (myKVO2)
    +(void)load{
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Class cls = [NSObject class];
            //交换系统的方法
            SEL originalSelector = @selector(removeObserver:forKeyPath:context:);
            SEL swizzledSelector = @selector(myRemoveObserver:forKeyPath:options:context:);
            Method originalMethod = class_getInstanceMethod(cls, originalSelector);
            Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
            BOOL didAddMethod = class_addMethod(cls,
                                                originalSelector,
                                                method_getImplementation(swizzledMethod),
                                                method_getTypeEncoding(swizzledMethod));
            if(didAddMethod){
                class_replaceMethod(cls,
                                    swizzledSelector,
                                    method_getImplementation(originalMethod),
                                    method_getTypeEncoding(originalMethod));
            }
            else{
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
            
            
            //交换系统的方法
            SEL originalSelector2 = @selector(addObserver:forKeyPath:options:context:);
            SEL swizzledSelector2 = @selector(myAddObserver:forKeyPath:options:context:);
            Method originalMethod2 = class_getInstanceMethod(cls, originalSelector2);
            Method swizzledMethod2 = class_getInstanceMethod(cls, swizzledSelector2);
            BOOL didAddMethod2 = class_addMethod(cls,
                                                originalSelector,
                                                method_getImplementation(swizzledMethod2),
                                                method_getTypeEncoding(swizzledMethod2));
            if(didAddMethod2){
                class_replaceMethod(cls,
                                    swizzledSelector2,
                                    method_getImplementation(originalMethod2),
                                    method_getTypeEncoding(originalMethod2));
            }
            else{
                method_exchangeImplementations(originalMethod2, swizzledMethod2);
            }
        });
    }
    
    
    - (void)myRemoveObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{
        if ([self hasContainedObserver:observer forKeyPath:keyPath context:context]) {
            [self myRemoveObserver:observer forKeyPath:keyPath options:options context:context];
        }
    }
    
    -(void)myAddObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{
        if (![self hasContainedObserver:observer forKeyPath:keyPath context:context]) {
            [self myAddObserver:observer forKeyPath:keyPath options:options context:context];
        }
    }
    
    -(BOOL)hasContainedObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context{
        id observationInfo = self.observationInfo;
        if (observationInfo) {
            NSArray *observances = [observationInfo valueForKey:@"_observances"];
            for (id observance in observances) {
                NSObject *_observer = [observance valueForKey:@"_observer"];
                NSString *_keyPath = [[observance valueForKeyPath:@"_property"] valueForKeyPath:@"_keyPath"];
                Ivar _contextIvar = class_getInstanceVariable([observance class], "_context");
                void *_context = (__bridge void *)(object_getIvar(observance, _contextIvar));
                if (_observer == observer && [_keyPath isEqualToString:keyPath] && _context == context) return YES;
            }
        }
        return NO;
    }
    @end
    
    实现:
    #import "KVOBaseUsesViewController_13.h"
    #import "Person_.h"
    #import "NSObject+myKVO2.h"
    
    @interface KVOBaseUsesViewController_13()
    @property (nonatomic,strong)Person_*p;
    @end
    
    @implementation KVOBaseUsesViewController_13
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.p = [Person_ new];
        [self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:@"1"];
        [self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:@"2"];
    }
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        self.p.age = 10;
    }
    
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
    }
    
    -(void)dealloc{
        [self.p removeObserver:self forKeyPath:@"age" context:@"1"];
        [self.p removeObserver:self forKeyPath:@"age" context:@"2"];
        [self.p removeObserver:self forKeyPath:@"age" context:@"1"];
        
        /*这样写会炸的哟,因为只叫唤了:removeObserver:forKeyPath:context:的方法
        [self.p removeObserver:self forKeyPath:@"age"];
        [self.p removeObserver:self forKeyPath:@"age"];
        [self.p removeObserver:self forKeyPath:@"age"];
         */
    }
    @end
    

    友情链接:

    相关文章

      网友评论

          本文标题:KVO的使用(二)

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