OC学习篇之---KVC和KVO操作
前一篇文章我们介绍了OC中最常用的文件操作:http://blog.csdn.net/jiangwei0910410003/article/details/41875015,那么今天来看一下OC中的一个比较有特色的知识点:KVC和KVO
一、KVC操作
OC中的KVC操作就和Java中使用反射机制去访问类的private权限的变量,很暴力的,这样做就会破坏类的封装性,本来类中的的private权限就是不希望外界去访问的,但是我们这样去操作,就会反其道而行,但是我们有时候真的需要去这样做,哎。所以说有些事不是都是顺其自然的,而是需要的时候自然就诞生了。
下面就来看一下这种技术的使用:
Dog.h
//
// Dog.h
// 42_KVC
//
// Created by jiangwei on 14-10-14.
// Copyright (c) 2014年 jiangwei. All rights reserved.
// #import <Foundation/Foundation.h> @interface Dog : NSObject @end
Dog.m
//
// Dog.m
// 42_KVC
//
// Created by jiangwei on 14-10-14.
// Copyright (c) 2014年 jiangwei. All rights reserved.
// #import "Dog.h" @implementation Dog @end
定义了Dog这个类,但是什么都没有,他只是一个中间类,没什么作用,在这个demo中。
Person.h
//
// Person.h
// 42_KVC
//
// Created by jiangwei on 14-10-14.
// Copyright (c) 2014年 jiangwei. All rights reserved.
// #import <Foundation/Foundation.h>
#import "Dog.h" @interface Person : NSObject{
@private
NSString *_name;
NSDog *_dog; NSInteger *age;
} @end
Person.m
//
// Person.m
// 42_KVC
//
// Created by jiangwei on 14-10-14.
// Copyright (c) 2014年 jiangwei. All rights reserved.
// #import "Person.h" @implementation Person - (NSString *)description{
NSLog(@"%@",_name);
return _name;
} @end
Person类中我们定义了两个属性,但是这两个属性对外是不可访问的,而且也没有对应的get/set方法。我们也实现了description方法,用于打印结果
看一下测试代码
main.m
//
// main.m
// 42_KVC
//
// Created by jiangwei on 14-10-14.
// Copyright (c) 2014年 jiangwei. All rights reserved.
// #import <Foundation/Foundation.h>
#import "Person.h"
#import "Dog.h" //KVC:很暴力,及时一个类的属性是私有的,而且也没有get/set方法,同样可以读写
//相当于Java中的反射,破坏类的封装性
int main(int argc, const char * argv[]) {
@autoreleasepool { Person *p = [[Person alloc] init]; //设置值
//这里setValue方法:第一个参数是value,第二个参数是key(就是类的属性名称)
[p setValue:@"jiangwei" forKey:@"name"]; Dog *dog = [[Dog alloc] init];
[p setValue:dog forKey:@"dog"]; //KVC设置值时,如果属性有set方法,则优先调用set方法,如果没有则直接设置上去,get方法类似 //读取值
NSString *name = [p valueForKey:@"name"]; //设置基本数据类型
//这里需要将基本类型转化成NSNumber
//在设置值的时候,会有自动解包的过程,NSNumber会解包赋值给age
[p setValue:@22 forKey:@"age"]; NSLog(@"%@",p); return 0;
}
return 0;
}
这里我们生成一个Person对象,然后开始使用KVC技术了:
1、设置属性值
//设置值
//这里setValue方法:第一个参数是value,第二个参数是key(就是类的属性名称)
[p setValue:@"jiangwei" forKey:@"name"]; Dog *dog = [[Dog alloc] init];
[p setValue:dog forKey:@"dog"];
使用setValue方法,就可以进行对属性进行设置值操作了,同时需要传递这个属性的名称,这个和Java中使用反射机制真的很像。
注:KVC设置值时,如果属性有set方法,则优先调用set方法,如果没有则直接设置上去,get方法一样
//设置基本数据类型
//这里需要将基本类型转化成NSNumber
//在设置值的时候,会有自动解包的过程,NSNumber会解包赋值给age
[p setValue:@22 forKey:@"age"];
还有一个需要注意的地方:当我们在设置基本类型的时候,需要将其转化成NSNumber类型的。
2、取属性的值
//读取值
NSString *name = [p valueForKey:@"name"];
取值就简单了
下面再来看一下KVC中强大的功能:键值路径
键值路径是对于一个类中有数组对象的属性进行便捷操作。
看个场景:
一个作者有多本书
Author.h
//
// Author.h
// 43_KeyValuePath
//
// Created by jiangwei on 14-10-15.
// Copyright (c) 2014年 jiangwei. All rights reserved.
// #import <Foundation/Foundation.h> @interface Author : NSObject{
NSString *_name; //作者出版的书,一个作者对应多个书籍对象
NSArray *_issueBook;
} @end
作者类中定义了名字和一个书籍数组
Author.m
//
// Author.m
// 43_KeyValuePath
//
// Created by jiangwei on 14-10-15.
// Copyright (c) 2014年 jiangwei. All rights reserved.
// #import "Author.h" @implementation Author @end
Book.h
//
// Book.h
// 43_KeyValuePath
//
// Created by jiangwei on 14-10-15.
// Copyright (c) 2014年 jiangwei. All rights reserved.
// #import <Foundation/Foundation.h>
#import "Author.h" @interface Book : NSObject{
Author *_author;
} @property NSString *name;
@property float *price; @end
定义了一个作者属性,书的名字,价格
Book.m
//
// Book.m
// 43_KeyValuePath
//
// Created by jiangwei on 14-10-15.
// Copyright (c) 2014年 jiangwei. All rights reserved.
// #import "Book.h" @implementation Book @end
看一下测试代码
main.m
//
// main.m
// 43_KeyValuePath
//
// Created by jiangwei on 14-10-15.
// Copyright (c) 2014年 jiangwei. All rights reserved.
// #import <Foundation/Foundation.h>
#import "Book.h"
#import "Author.h" int main(int argc, const char * argv[]) {
@autoreleasepool { //------------------KVC键值路径
/*
Book *book = [[Book alloc] init];
Author *author = [[Author alloc] init]; //设置作者
[book setValue:author forKey:@"author"]; //设置作者的名字
//路径为:author.name,中间用点号进行连接
[book setValue:@"jiangwei" forKeyPath:@"author.name"];
NSString *name = [author valueForKey:@"name"];
NSLog(@"name is %@",name);
*/ //--------------------KVC的运算
Author *author = [[Author alloc] init];
[author setValue:@"莫言" forKeyPath:@"name"]; Book *book1 = [[Book alloc] init];
book1.name = @"红高粱";
book1.price = 9;
Book *book2 = [[Book alloc] init];
book2.name = @"蛙";
book2.price = 10;
NSArray *array = [NSArray arrayWithObjects:book1,book2, nil];
[author setValue:array forKeyPath:@"issueBook"]; //基本数据类型会自动被包装成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"]; }
return 0;
}
1、首先通过前面说到的KVC设置作者的书籍数组
//--------------------KVC的运算
Author *author = [[Author alloc] init];
[author setValue:@"莫言" forKeyPath:@"name"]; Book *book1 = [[Book alloc] init];
book1.name = @"红高粱";
book1.price = 9;
Book *book2 = [[Book alloc] init];
book2.name = @"蛙";
book2.price = 10;
NSArray *array = [NSArray arrayWithObjects:book1,book2, nil];
[author setValue:array forKeyPath:@"issueBook"];
添加了两本书籍
2、下面就开始用到KVC中键值路径了
1)获取作者类中书籍数组中所有书籍的价格
//基本数据类型会自动被包装成NSNumber,装到数组中
//得到所有书籍的价格
NSArray *priceArray = [author valueForKeyPath:@"issueBook.price"];
NSLog(@"%@",priceArray);
看到了:@"issueBook.price" 这就是键值路径的使用,issueBook是作者类中的书籍数组属性名,price是书籍类的属性,中间用点号进行连接,这样我们就可以获取到了所有书籍的价格了,如果在Java中,我们需要用一个循环操作。但是OC中多么方便。
2)获取作者类中书籍数组的大小
//获取数组的大小
NSNumber *count = [author valueForKeyPath:@"issueBook.@count"];
NSLog(@"count=%@",count);
使用 @"issueBook.@count" 键值路径获取书籍数组的大小,issueBook是作者类中的书籍数组属性名,@count是特定一个写法,可以把它想象成一个方法,中间任然用点号进行连接
3)获取作者类中书籍数组的价格总和
//获取书籍价格的总和
NSNumber *sum = [author valueForKeyPath:@"issueBook.@sum.price"];
NSLog(@"%@",sum);
使用 @"issueBook.@sum.price" 键值路径获取书籍数组中的价格总和,issueBook是作者类中的书籍数组属性名,@sum是特性写法,可以把它想象成一个方法,price是书籍的价格属性名,可以把它看成是@sum的一个参数,中间用点号进行连接
如果在java中,这个需要用一个循环来计算总和,OC中很方便的
4)获取作者类中书籍数组的价格平均值、最小值、最大值
//获取书籍的平均值
NSNumber *avg = [author valueForKeyPath:@"issueBook.@avg.price"];
NSLog(@"%@",avg); //获取书籍的价格最大值和最小值
NSNumber *max = [author valueForKeyPath:@"issueBook.@max.price"];
NSNumber *min = [author valueForKeyPath:@"issueBook.@min.price"];
操作和上面类似,这里就不解释了
我们看到上面返回来的数据都是NSNumber类型的
二、KVO操作
KVO操作在OC中也是经常会用到的,而且这种机制在java中不存在的。
它的作用就是用来监听类中属性值的变化,实现原理是观察者模式,当然我们也可以使用观察者模式在Java中实现这样的机制
看一下具体的例子:现在有一个小孩类,他有两个属性:开心值,饥饿值,然后还有一个护士类,用来监听孩子类的这两个属性值的
Chidren.h
//
// Children.h
// 44_KVO
//
// Created by jiangwei on 14-10-16.
// Copyright (c) 2014年 jiangwei. All rights reserved.
// #import <Foundation/Foundation.h> @interface Children : NSObject @property NSInteger *hapyValue;
@property NSInteger *hurryValue; @end
Children.m
//
// Children.m
// 44_KVO
//
// Created by jiangwei on 14-10-16.
// Copyright (c) 2014年 jiangwei. All rights reserved.
// #import "Children.h" @implementation Children - (id) init{
self = [super init];
if(self != nil){
//启动定时器
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
self.hapyValue= 100;
}
return self;
} - (void) timerAction:(NSTimer *) timer{
//使用set方法修改属性值,才能触发KVO int value = _hapyValue;
[self setHapyValue:--value]; int values = _hurryValue;
[self setHurryValue:--values];
} @end
在初始化方法中,我们启动一个定时器,然后隔1s就去修改孩子类的值
Nure.h
//
// Nure.h
// 44_KVO
//
// Created by jiangwei on 14-10-16.
// Copyright (c) 2014年 jiangwei. All rights reserved.
// #import <Foundation/Foundation.h> @class Children;
@interface Nure : NSObject{
Children *_children;
} - (id) initWithChildren:(Children *)children; @end
定义一个孩子属性
Nure.m
//
// Nure.m
// 44_KVO
//
// Created by jiangwei on 14-10-16.
// Copyright (c) 2014年 jiangwei. All rights reserved.
// #import "Nure.h"
#import "Children.h" @implementation Nure - (id) initWithChildren:(Children *)children{
self = [super init];
if(self != nil){
_children = children; //观察小孩的hapyValue
//使用KVO为_children对象添加一个观察者,用于观察监听hapyValue属性值是否被修改
[_children addObserver:self forKeyPath:@"hapyValue" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:@"context"]; //观察小孩的hurryValue
[_children addObserver:self forKeyPath:@"hurryValue" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:@"context"];
}
return self;
} //触发方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
NSLog(@"%@",change);
//通过打印change,我们可以看到对应的key //通过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){
//do something...
}
}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 < 90){
//do something...
}
} NSLog(@"%@",context);//打印的就是addObserver方法的context参数 //使用KVC去修改属性的值,也会触发事件
} - (void)dealloc{ //移除观察者
[_children removeObserver:self forKeyPath:@"hapyValue"];
[_children removeObserver:self forKeyPath:@"hurryValue"]; } @end
看到了在这里就开始进行监听操作了
下面来具体看一下如何做到监听的
1、添加监听对象
我们使用addObserver方法给孩子添加监听对象
第一个参数:监听者,这里是Nure,所以可以直接传递self
第二个参数:监听对象的属性名
第三个参数:监听这个属性的状态:这里可以使用|进行多种组合操作,属性的新值和旧值
第四个参数:传递内容给监听方法
//观察小孩的hapyValue
//使用KVO为_children对象添加一个观察者,用于观察监听hapyValue属性值是否被修改
[_children addObserver:self forKeyPath:@"hapyValue" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:@"context"]; //观察小孩的hurryValue
[_children addObserver:self forKeyPath:@"hurryValue" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:@"context"];
2、监听方法
//触发方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
NSLog(@"%@",change);
//通过打印change,我们可以看到对应的key //通过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){
//do something...
}
}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 < 90){
//do something...
}
} NSLog(@"%@",context);//打印的就是addObserver方法的context参数 //使用KVC去修改属性的值,也会触发事件
}
我们上面传递的第一个参数是监听者,这个方法也是在监听者中实现的,当属性值发生变化的时候,这个方法会被回调
这个方法的参数:
第一个参数:键值路径
第二个参数:监听对象
第三个参数:变化的值
第四个参数:传递的内容
我们看到代码中有一个特殊的参数:第三个参数:NSDirctionary类型的
其实我们如果不知道是干什么的,我们可以打印一下他的结果看一下,很简单,这里就不截图说明了
我们会发现他有两个键值对
key是:new和old
他们就是分别代表这个属性值变化的前后值,同时他们的得到也和之前我们添加监听对象时设置的第三个参数有关:
NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld
那个地方设置了几种状态,这里的NSDirctionary中就会有几个键值对
3、销毁方法
这个并不属于KVO的内容了,只是在这里用到了就顺便说一下
- (void)dealloc{ //移除观察者
[_children removeObserver:self forKeyPath:@"hapyValue"];
[_children removeObserver:self forKeyPath:@"hurryValue"]; }
我们在创建一个对象的时候会调用alloc方法,当对象被销毁的时候会调用dealloc这个方法,这个和C++中的析构函数一样,Java中有垃圾回收器,所以没有此类的方法,但是有一个finalize方法,其实这个方法就是在垃圾回收器回收对象的时候会调用,和这个功能差不多,但是在Java中,我们并不提倡使用这个方法。因为会造成GC的回收发生错误。
我们在销毁方法中需要移除监听者
总结
这一篇就介绍了OC中比较有特色的两个机制:KVC和KVO
KVC:就是可以暴力的去get/set类的私有属性,同时还有强大的键值路径对数组类型的属性进行操作
KVO:监听类中属性值变化的
OC学习篇之---KVC和KVO操作的更多相关文章
- OC学习篇之---总结和学习目录
今天终于把OC的基础知识学习完了,但是这些知识只是最基础的,还有很多高级知识,这个可能需要后面慢慢的去学习才能体会到.下面就是这次学习OC的目录教程,如果大家发现有什么不正确的地方,请指正,小弟是新生 ...
- OC学习篇之---单例模式
在之前的一片文章中介绍了对象的拷贝相关知识:http://blog.csdn.net/jiangwei0910410003/article/details/41926531,今天我们来看一下OC中的单 ...
- OC学习篇之---通知(NSNotificationCenter)
在前一篇文章中我们介绍了OC中很常用的两个技术:KVC和KVO: http://blog.csdn.net/jiangwei0910410003/article/details/41912937,今天 ...
- (转载)OC学习篇之---概述
前言 终于开启了OC的学习篇了,之前由于工作上的事,学习就一直搁浅了,不过最近由于各种原因,感觉必须要开启iOS的开发旅程了,不然就老了.因为之前一直是做Android的,所以学习iOS来就没那么费劲 ...
- ios开发runtime学习五:KVC以及KVO,利用runtime实现字典转模型
一:KVC和KVO的学习 #import "StatusItem.h" /* 1:总结:KVC赋值:1:setValuesForKeysWithDictionary实现原理:遍历字 ...
- OC学习篇之---文件的操作
今天我们来介绍OC中文件操作,在之前的文章中,已经接触到了文件的创建了,但是那不是很具体和详细,这篇文章我们就来仔细看一下OC中是如何操作文件的: 第一.首先来看一下本身NSString类给我们提供了 ...
- (转载)OC学习篇之---类的三大特性:封装,继承,多态
之前的一片文章介绍了OC中类的初始化方法和点语法的使用,今天来继续学习OC中的类的三大特性,我们在学习Java的时候都知道,类有三大特性:继承,封装,多态,这个也是介绍类的时候,必须提到的话题,那么今 ...
- OC学习篇之---类的三大特性(封装,继承,多态)
之前的一片文章介绍了OC中类的初始化方法和点语法的使用:http://blog.csdn.net/jiangwei0910410003/article/details/41683873,今天来继续学习 ...
- (转载)OC学习篇之---归档和解挡
前几篇文章说到了OC中的Foundation框架,今天我们来看一下OC中的一个重要知识点:归档 OC中的归档就是将对象写入到一个文件中,Java中的ObjectInputStream和ObjectOu ...
随机推荐
- POJ 3481 Double Queue (treap模板)
Description The new founded Balkan Investment Group Bank (BIG-Bank) opened a new office in Bucharest ...
- C#操作xml完整类文件
C#操作xml完整类文件 xml_oper.cs using ...System; using System.Data; using System.Web; using System.Xml; /** ...
- BZOJ 5137: [Usaco2017 Dec]Standing Out from the Herd(后缀自动机)
传送门 解题思路 这个似乎和以前做过的一道题很像,只不过这个是求本质不同子串个数.肯定是先把广义\(SAM\)造出来,然后\(dfs\)时把子节点的信息合并到父节点上,看哪个只被一个串覆盖,\(ans ...
- 4 November in ss
Contest A. 输油管道问题 某石油公司计划建造一条由东向西的主输油管道.该管道要穿过一个有 \(n\) 口油井的油田.从每口油井都要有一条输油管道沿最短路经 (或南或北) 与主管道相连.如果给 ...
- 判断PC或者是APP
function isPC() { if (/Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent)) { return fa ...
- QString的arg方法
第一个参数是要填充的数字,第二个参数为最小宽度,第三个参数为进制,第四个参数为当原始数字长度不足最小宽度时用于填充的字符,如 QString name=QString("R%1C%2&quo ...
- 2.Jmeter 如何在jsr223 脚本中停止测试任务
Jmeter 如何在jsr223 脚本中停止测试任务 在可以直接引用ctx的变量的processor中可以执行如下脚本即可. (例如jsr223 postprocessor中) ctx.getEngi ...
- 搭建邮件服务器 使用Postfix与Dovecot
首先需要从yum中下载安装三个服务:bind-chroot postfix dovecot 配置文件依次: /etc/named.conf 下载安装完后要开启的服务:named ...
- iphoneX的适配问题
iphoneX();function iphoneX(){ var oMeta = document.createElement('meta'); oMeta.setAttribute('name', ...
- ASP.NET Core 2.1 JWT Token 使用 (二) - 简书
原文:ASP.NET Core 2.1 JWT Token 使用 (二) - 简书 接上文,https://www.jianshu.com/p/c5f9ea3b4b65 ASP.NET Core 2. ...