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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了