一、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:@"issueBook.@count"]; NSLog(@"count=%@",count); //获取书籍价格的总和 NSNumber *sum = [author valueForKeyPath:@"issueBook.@sum.price"]; NSLog(@"%@",sum); //获取书籍的平均值 NSNumber *avg = [author valueForKeyPath:@"issueBook.@avg.price"]; NSLog(@"%@",avg); //获取书籍的价格最大值和最小值 NSNumber *max = [author valueForKeyPath:@"issueBook.@max.price"]; NSNumber *min = [author valueForKeyPath:@"issueBook.@min.price"]; 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改变,实现抽屉效果。