iOS开发:KVC与KVO
程序员文章站
2024-03-24 13:33:34
...
一、KVC
1、介绍:
KVC 就是键值编码(key-value-coding),可以直接访问对象的属性,或者给对象的属性赋值。黑魔法之一,很多高级的iOS开发技巧都是基于KVC实现的。
2、应用:
- KVC设值
- KVC取值
- KVC使用keyPath
- KVC处理异常
- KVC处理数值和结构体类型属性
- KVC键值验证(Key-Value Validation)
- KVC处理集合
-
KVC数模转换
3、实现:
#import <Foundation/Foundation.h>
@interface Author : NSObject{
NSString *_name;
//作者出版的书籍,一个作者对应多个书籍对象
NSArray *_issueBook;
}
@end
//---------------------
#import "Author.h"
@implementation Author
@end
#import <Foundation/Foundation.h>
#import "Author.h"
@interface Book : NSObject{
Author *_author;
}
//名字
@property(nonatomic,copy) NSString *name;
///价格
@property(nonatomic,assign)float price;
@end
//---------------------
#import "Book.h"
@implementation Book
@end
1、KVC赋值
通过键值路径为对象的属性赋值。一般用于私有的属性赋值:
//---------- KVC键值编码 --------
Author *author = [[Author alloc] init];
//设置属性值
[author setValue:@"莫言" forKeyPath:@"name"];
如果对象A中的属性含有是一个对象B,设置对象B的属性:
//------- KVC设置作者的书籍数组 -------
//键值路径:对于一个类中有数组对象的属性进行便捷操作
Book *book1 = [[Book alloc] init];
book1.name = @"红高粱";
book1.price = 9;
Book *book2 = [[Book alloc] init];
book2.name = @"蛙";
book2.price = 6;
NSArray *array = [NSArray arrayWithObjects:book1,book2, nil];
[author setValue:array forKeyPath:@"issueBook"];
2、KVC取值
通过键值路径获取属性的值。一般通过key值获得私有属性的值。
//获取属性值
NSString *name = [author valueForKey:@"name"];
NSLog(@"%@",name);
可以通过keypath获得值:
// ----- KVC中键值路径取值 -------
//基本数据类型会自动被包装成NSNumber,装到数组中
//得到所有书籍的价格
NSArray *priceArray = [author valueForKeyPath:@"issueBook.price"];
NSLog(@"%@",priceArray);
//获取数组的大小
NSNumber *count = [author valueForKeyPath:@"aaa@qq.com"];
NSLog(@"count=%@",count);
//获取书籍价格的总和
NSNumber *sum = [author valueForKeyPath:@"aaa@qq.com"];
NSLog(@"%@",sum);
//获取书籍的平均值
NSNumber *avg = [author valueForKeyPath:@"aaa@qq.com"];
NSLog(@"%@",avg);
//获取书籍的价格最大值和最小值
NSNumber *max = [author valueForKeyPath:@"aaa@qq.com"];
NSNumber *min = [author valueForKeyPath:@"aaa@qq.com"];
NSLog(@"%@____%@",max,min);
模型转换
将字典转型成Model,方法:setValuesForKeysWithDictionary:
///数模转换
-(void)dicToModel{
// 定义一个字典
NSDictionary *dict = @{
@"name" : @"rattan",
@"price" : @"88.66",
};
// 创建模型
Book *p = [[Book alloc] init];
// 字典转模型
[p setValuesForKeysWithDictionary:dict];
NSLog(@"\n作者:%@\n价格:%.2lf",p.name,(float)p.price);
}
打印结果:
注意:字典的key和Model的属性一定要一一对应。否则系统会报错如下:
‘[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.’
二、KVO
KVO 是键值观察者(key-value-observing)。实现方式:通过对某个对象的某个属性添加观察者,当该属性改变,就会调用”observeValueForKeyPath:”方法,为我们提供一个“对象值改变了!”的时机进行一些操作。
1、原理:
当某个类的对象第一次被观察时,系统在运行时会创建该类的派生类,改派生类中重写了该对象的setter方法,并且在setter方法中实现了通知的机制。派生类重写了class方法,以“欺骗”外部调用者他就是原先那个类。系统将这个类的isa指针指向新的派生类,因此改对象也就是新的派生类的对象了。因而改对象调用setter就会调用重写的setter,从而**键值通知机制。此外派生类还重写了delloc方法来释放资源。
2、应用:
#import <Foundation/Foundation.h>
@interface Chidren : NSObject
@property(nonatomic,assign)NSInteger hapyValue;
@property(nonatomic,assign)NSInteger hurryValue;
///添加定时器
-(void)addTimer;
///注销定时器
-(void)deallocTimer;
@end
// -----------------------------
#import "Chidren.h"
@interface Chidren()
///定时器
@property(nonatomic,strong)NSTimer *timer;
@end
@implementation Chidren
- (id) init{
self = [super init];
if(self != nil){
self.hapyValue= 100;
[self addTimer];
}
return self;
}
///添加定时器
-(void)addTimer{
//启动定时器
[self timer];
}
- (void)timerAction:(NSTimer *)timer{
//使用set方法修改属性值,才能触发KVO
NSInteger value = _hapyValue;
// [self setHapyValue:--value];
self.hapyValue = --value;
NSInteger values = _hurryValue;
[self setHurryValue:--values];
}
///注销定时器
-(void)deallocTimer{
[_timer invalidate];
_timer = nil;
}
-(void)dealloc{
[self deallocTimer];
}
#pragma mark - lazyload
-(NSTimer *)timer{
if (!_timer) {
_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
}
return _timer;
}
@end
#import <Foundation/Foundation.h>
#import "Chidren.h"
@interface Nure : NSObject
- (id) initWithChildren:(Chidren *)children;
@end
// ----------------------------
#import "Nure.h"
@implementation Nure{
Chidren *_children;
}
static NSInteger isGo = 0;
#pragma mark - initial
- (id) initWithChildren:(Chidren *)children{
self = [super init];
if(self != nil){
_children = children;
isGo = 0;
[self addKVO];
}
return self;
}
///添加KVO观察者
-(void)addKVO{
//观察小孩的hapyValue
//使用KVO为_children对象添加一个观察者,用于观察监听hapyValue属性值是否被修改
[_children addObserver:self forKeyPath:@"hapyValue" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:@"hapyValue"];
//观察小孩的hurryValue
[_children addObserver:self forKeyPath:@"hurryValue" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:@"hurryValue"];
}
#pragma mark - NSKeyValueObserving
/**
触发方法:当属性值发生变化的时候,这个方法会被回调
@param keyPath 键值路径
@param object 监听对象
@param change 变化的值
@param context 传递的内容
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
//通过打印change,我们可以看到对应的key
NSLog(@"%@:%@",context,change);
//通过keyPath来判断不同属性的观察者
if([keyPath isEqualToString:@"hapyValue"]){
//这里change中有old和new的值是因为我们在调用addObserver方法时,用到了
//NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;想要哪一个就用哪一个
//[change objectForKey:@"old"]是修改前的值
NSNumber *hapyValue = [change objectForKey:@"new"];//修改之后的最新值
NSInteger value = [hapyValue integerValue];
if(value < 90){
[_children deallocTimer];
if (isGo == 0) {
isGo = 1;
NSLog(@"休息一下");
sleep(5);
}
[_children addTimer];
}
}else if([keyPath isEqualToString:@"hurryValue"]){
//这里change中有old和new的值是因为我们在调用addObserver方法时,用到了
//NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;想要哪一个就用哪一个
//[change objectForKey:@"old"]是修改前的值
NSNumber *hurryValue = [change objectForKey:@"new"];//修改之后的最新值
NSInteger value = [hurryValue integerValue];
if(value < -30){
//do something...
NSLog(@"退出循环");
[_children deallocTimer];
}
}
//使用KVC去修改属性的值,也会触发事件
}
- (void)dealloc{
//移除观察者
[_children removeObserver:self forKeyPath:@"hapyValue"];
[_children removeObserver:self forKeyPath:@"hurryValue"];
}
@end
#import "Nure.h"
@interface KVOVC ()
///小孩
@property(nonatomic,strong)Chidren *child;
///护士
@property(nonatomic,strong)Nure *nure;
@end
@implementation KVOVC
#pragma mark - initial
- (void)viewDidLoad {
[super viewDidLoad];
[self initView];
}
#pragma mark - method
-(void)buttonClick:(UIButton *)sender{
NSLog(@"开启键值观察者模式");
[self removeObj];
[self nure];
}
-(void)removeObj{
_child = nil;
_nure = nil;
}
#pragma mark - lazyload
-(Nure *)nure{
if (!_nure) {
_nure = [[Nure alloc]initWithChildren:self.child];
}
return _nure;
}
-(Chidren *)child{
if (!_child) {
_child = [[Chidren alloc]init];
}
return _child;
}
4、使用场景:用于监听对象属性的改变
- (1)下拉刷新、下拉加载监听UIScrollView的contentoffsize;
- (2)webview混排监听contentsize;
- (3)监听模型属性实时更新UI;
- (4)监听控制器frame改变,实现抽屉效果。