KVO的使用(二)

2019-01-08  本文已影响0人  dandelionYD

接上次: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

友情链接:

上一篇下一篇

猜你喜欢

热点阅读