KVO的使用(二)

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

友情链接:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,103评论 1 32
  • 概要 64学时 3.5学分 章节安排 电子商务网站概况 HTML5+CSS3 JavaScript Node 电子...
    阿啊阿吖丁阅读 9,208评论 0 3
  • 今天又下雪了,寒冷的冬天还没有离开莫城。出门的人又套上了厚重的羽绒服。肥胖的毛熊们扭动着肥硕的屁股在一夜新雪过后地...
    虞美忍阅读 167评论 0 0
  • 蓝天秋水长, 云淡百花香。 碧水起涟漪, ...
    情爱千秋阅读 1,344评论 28 59
  • 红墙绿瓦萧索 屋顶在天际被云朵奚落 小巷纵横交错 你已离开 飞鸟已过 小雨淅淅落落 林间隐隐绰绰 有心事的人不止我...
    一场荒唐阅读 198评论 5 1