iOS基础 - KVO

▶ KVO 简介

1 - 工作原理

① 被观察者发出 addObserver:forKeyPath:options:context: 方法来添加观察者

② 只要被观察者的 keyPath 值变化,就会在观察者里调用方法 observeValueForKeyPath:ofObject:change:context: 就是说观察者需要实现该方法来对 KVO 发出的通知做出响应

2 - KVO 特点

① 这些代码都只需在观察者里进行实现,被观察者不用添加任何代码。就是说,谁要做监听,谁就进行注册,然后对响应进行处理,使得观察者与被观察者完全解耦

② 但是 KVO 只能检测类中的属性,并且属性名都是通过 NSString 来查找,编译器不会帮你检错和补全,手码很容易出错

③ 不管是 MRC 环境还是 ARC  环境,都需要重写 dealloc,需要在其中移除观察者

▶ 使用 KVO

场景一:将 Person 实例对象置成被观察者,然后在观察者 ViewController 中监听其属性变化

//  - Person.h

#import <Foundation/Foundation.h>
@interface Person : NSObject
@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)NSInteger age;
@end

// - Person.m

复制代码
#import "Person.h"
@implementation Person
- (void)dealloc{
    self.name = nil;
    [super dealloc];
}
- (NSString *)description{
    return [NSString stringWithFormat:@"name = %@,age = %d",self.name,self.age];
}
@end
复制代码

// - ViewController.h

#import <UIKit/UIKit.h>
@class Person;
@interface ViewController : UIViewController
@property(retain,nonatomic)Person *person;
@end

// - ViewController.m

复制代码
 1 #import "ViewController.h"
 2 #import "Person.h"
 3 @implementation ViewController
 4 
 5 //// MRC
 6 //- (void)dealloc {
 7 //    // 移除观察者
 8 //    [_person removeObserver:self forKeyPath:@"age"];
 9 //    [_person release];
10 //    [super dealloc];
11 //}
12 
13 // ARC
14 -(void)dealloc{
15     [_person removeObserver:self forKeyPath:@"age"];
16 }
17 
18 - (void)viewDidLoad {
19     [super viewDidLoad];
20     
21     _person = [[Person alloc] init];
22     // 给 _person 添加观察者,用来监听 age属性
23     [_person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
24     // age 发生改变,触发监听方法
25     self.person.age = 6;
26 }
27 
28 
29 // 谁成为观察者,谁就重写此方法,否则 crash
30 -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
31     
32     NSLog(@"---valueForKeyPath = %@",keyPath);
33     NSLog(@"---ofObject = %@",object);
34     // 根据 change 字典里的 kind,可用来区分对象做的是什么操作
35     NSLog(@"---change = %@",change);
36     NSLog(@"---context = %@",context);
37 }
38 
39 @end
复制代码

日志信息

场景二:下面我们来验证 属性赋值、KVC赋值 两种方式,是否均可触发监听

// - Person.h

#import <Foundation/Foundation.h>
@interface Person : NSObject
@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)NSInteger age;
-(void)changeAge:(NSInteger)age;
@end

// - Person.m

复制代码
 1 #import "Person.h"
 2 @implementation Person
 3 
 4 -(NSString*)description{
 5     return [NSString stringWithFormat:@"name = %@,age = %ld",self.name,self.age];
 6 }
 7 
 8 // 重写 KVC 方法
 9 -(void)setValue:(id)value forUndefinedKey:(NSString *)key{
10 
11     NSLog(@"该对象没有声明%@属性",key);
12 }
13 
14 // 重写 KVC 方法
15 -(id)valueForUndefinedKey:(NSString *)key{
16 
17     NSLog(@"该对象没有声明%@属性",key);
18     return nil;
19 }
20 
21 //// 测试一:直接赋值,不触发 KVO
22 //-(void)changeAge:(NSInteger)age{
23 //    _age = age;
24 //}
25 
26 //// 测试二:点语法赋值,触发 KVO
27 //-(void)changeAge:(NSInteger)age{
28 //    self.age = age;
29 //}
30 
31 @end
复制代码

// - ViewController.h

#import <UIKit/UIKit.h>
@class Person;
@interface ViewController : UIViewController
@property(strong,nonatomic)Person *person;
@end

// - ViewController.m

复制代码
 1 #import "ViewController.h"
 2 #import "Person.h"
 3 @implementation ViewController
 4 
 5 //// MRC
 6 //- (void)dealloc {
 7 //    [_person removeObserver:self forKeyPath:@"age"];
 8 //    [_person release];
 9 //    [super dealloc];
10 //}
11 
12 // ARC
13 - (void)dealloc{
14     [_person removeObserver:self forKeyPath:@"age"];
15 }
16 
17 - (void)viewDidLoad {
18     [super viewDidLoad];
19     
20     _person = [[Person alloc] init];
21     [_person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
22     self.person.age = 6;
23     // 模拟不断改变 age 的值时,对直接赋值、点语法赋值进行测试
24     [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timeAction) userInfo:nil repeats:YES];
25 }
26 
27 static int number = 0;// 年龄
28 
29 -(void)timeAction{
30     
31 //    // 测试一 changeAge:内部直接赋值
32 //    [_person changeAge:number ++]; // 不触发 KVO
33     
34     
35 //    // 测试二 changeAge:点语法赋值
36 //    [_person changeAge:number ++]; // 触发 KVO
37     
38     
39 //    // 测试三:属性赋值
40 //    self.person.age ++;// 触发 KVO
41     
42     // 测试四:KVC 赋值
43     [_person setValue:[NSNumber numberWithInt:number++ ]  forKey:@"age"]; // 触发 KVO
44 }
45 
46 // 重写监听方法
47 -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
48     
49     NSLog(@"---valueForKeyPath = %@",keyPath);
50     NSLog(@"---ofObject = %@",object);
51     NSLog(@"---change = %@",change);
52     NSLog(@"---context = %@",context);
53 }
54 
55 @end
复制代码

日志信息

测试一:setter 内部直接赋值,不触发监听

测试二:setter 内部点语法赋值,触发监听

测试三:属性赋值,触发监听

测试四:KVC 赋值,触发监听

注:触发监听时,只能是通过调用属性的 setter 方法或使用 KVC 赋值,直接赋值的方法是不会触发 KVO 的

场景三:数组对于 KVO 是个比较特殊的存在!下面我们来验证 KVO 对数组的监听判定

复制代码
 1 #import "ViewController.h"
 2 @interface ViewController ()
 3 @property(strong,nonatomic)NSMutableArray *dataArray;
 4 @end
 5 
 6 @implementation ViewController
 7 
 8 - (void)viewDidLoad {
 9     [super viewDidLoad];
10     
11     self.dataArray = [NSMutableArray arrayWithCapacity:0];
12     
13     // 当前对象注册一个观察者,监听自身数组的变化
14     [self addObserver:self  forKeyPath:@"dataArray" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
15     // 往数组中添加数据,并不会触发监听方法,why ? ? ?
16     [self.dataArray addObject:@"Solina"];// KVO 并不会获取到 self.dataArray 的值,监听不到数组的变化
17     
18     // 方式一
19     // 通过 NSObject 的 mutableArrayValueForKey 方法获取到可变数组,进行增删改查时可触发 KVO
20     [[self mutableArrayValueForKey:@"dataArray"] addObject:@"Jack"];// 添加
21     [[self mutableArrayValueForKey:@"dataArray"] insertObject:@"Manu" atIndex:0];// 插入
22     [[self mutableArrayValueForKey:@"dataArray"] replaceObjectAtIndex:1 withObject:@"Manu"];// 替换
23     
24     // 方式二
25     // 通过调用属性动态生成的方法(需要时重写对应的方法),触发 KVO
26     [self insertObject:@"Keomir" inDataArrayAtIndex:1];// 插入
27     [self replaceObjectInDataArrayAtIndex:0 withObject:@"Saxon"];// 替换
28 }
29 
30 // 监听方法
31 -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
32     NSLog(@"%@",change);
33 }
34 
35 //----------------- 重写数组属性动态生成的方法 --------------------
36 // 编译器会根据我们声明的数组属性,动态的生成这几个方法,需要时重写即可
37 // 插入
38 -(void)insertObject:(id)object inDataArrayAtIndex:(NSUInteger)index{
39     [self.dataArray insertObject:object atIndex:index];
40 }
41 
42 // 删除
43 -(void)removeObjectFromDataArrayAtIndex:(NSUInteger)index{
44     [self.dataArray removeObjectAtIndex:index];
45 }
46 
47 // 替换
48 -(void)replaceObjectInDataArrayAtIndex:(NSUInteger)index withObject:(id)object{
49     [self.dataArray replaceObjectAtIndex:index withObject:object];
50 }
51 
52 @end
复制代码

 

posted on   低头捡石頭  阅读(16)  评论(0编辑  收藏  举报

编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

导航

统计

点击右上角即可分享
微信分享提示