文档章节

KVC和KVO操作

 脑残程序猿
发布于 2015/06/26 20:38
字数 3227
阅读 12
收藏 1
点赞 0
评论 0

一、KVC操作

OC中的KVC操作就和Java中使用反射机制去访问类的private权限的变量,很暴力的,这样做就会破坏类的封装性,本来类中的的private权限就是不希望外界去访问的,但是我们这样去操作,就会反其道而行,但是我们有时候真的需要去这样做,哎。所以说有些事不是都是顺其自然的,而是需要的时候自然就诞生了。

下面就来看一下这种技术的使用:

Dog.h

#import <Foundation/Foundation.h>
@interface Dog : NSObject
@end

Dog.m

#import "Dog.h"
@implementation Dog
@end

定义了Dog这个类,但是什么都没有,他只是一个中间类,没什么作用,在这个demo中。

Person.h

#import <Foundation/Foundation.h>
#import "Dog.h"@interface Person : NSObject{
@private  NSString *_name;  
NSDog *_dog;  
NSInteger *age;
}
@end

Person.m

#import "Person.h"@implementation Person

- (NSString *)description{
    NSLog(@"%@",_name);    
    return _name;
}
@end

Person类中我们定义了两个属性,但是这两个属性对外是不可访问的,而且也没有对应的get/set方法。我们也实现了description方法,用于打印结果

看一下测试代码

main.m

#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

#import <Foundation/Foundation.h>
@interface Author : NSObject{  
NSString *_name;  
//作者出版的书,一个作者对应多个书籍对象  
NSArray *_issueBook;
}
@end

作者类中定义了名字和一个书籍数组

Author.m

#import "Author.h"
@implementation Author
@end

Book.h

#import <Foundation/Foundation.h>
#import "Author.h"
@interface Book : NSObject{  
Author *_author;
}
@property NSString *name;
@property float *price;
@end

定义了一个作者属性,书的名字,价格

Book.m

#import "Book.h"
@implementation Book
@end

看一下测试代码

main.m

#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

#import <Foundation/Foundation.h>
@interface Children : NSObject
@property NSInteger *hapyValue;
@property NSInteger *hurryValue;
@end

Children.m

#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

#import <Foundation/Foundation.h>
@class Children;
@interface Nure : NSObject{  
Children *_children;
}
- (id) initWithChildren:(Children *)children;
@end

定义一个孩子属性

Nure.m

#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、销毁方法

- (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:监听类中属性值变化的


© 著作权归作者所有

共有 人打赏支持
粉丝 0
博文 8
码字总数 8954
作品 0
广州
KVC/KVO 的使用及原理分析

KVC/KVO 概念 KVC : 即 Key-Value-Coding,用于键值编码。作为 cocoa 的一个标准化组成部分,它是基于 NSKeyValueCoding 非正式协议的机制。简单来说,就是直接通过 key 值对对象的属性进行...

满脸胡茬的小码农 ⋅ 2017/11/14 ⋅ 0

iOS基础黑科技:KVO(键值观察)以及KVC(键值编码)

一、前言: 许多大神的博客都有关于KVO以及KVC的分析,在这里我整理一下自己关于KVO以及KVC的理解。求大神们轻喷~~ 二、基本概念: 1、键值编码(KVC):顾名思义,键值编码是一种访问对象属...

MichaelLee_ ⋅ 2017/12/19 ⋅ 0

KVC 与 KVO 理解

KVC 与 KVO 是 Objective C 的关键概念,个人认为必须理解的东西,下面是实例讲解。 Key-Value Coding (KVC) KVC,即是指 NSKeyValueCoding,一个非正式的 Protocol,提供一种机制来间接访问...

Im刘亚芳 ⋅ 2014/12/04 ⋅ 0

iOS编程——Objective-C KVO/KVC机制

这两天在看和这个相关的的内容,全部推翻重写一个版本,这是公司内做技术分享的文档总结,对结构、条理做了更清晰的调整。先找了段代码,理解下,网上看到最多的一段的关于KVC的代码 先上代码...

尛瘋孑 ⋅ 2014/06/10 ⋅ 0

KVO和KVC的关系

KVC 与 KVO 是 Objective C 的关键概念,个人认为必须理解的东西,下面是实例讲解。 Key-Value Coding (KVC) KVC,即是指 NSKeyValueCoding,一个非正式的 Protocol,提供一种机制来间接访问...

刘三火 ⋅ 2016/04/21 ⋅ 0

OC学习篇之---KVC和KVO操作

前一篇文章我们介绍了OC中最常用的文件操作:http://blog.csdn.net/jiangwei0910410003/article/details/41875015,那么今天来看一下OC中的一个比较有特色的知识点:KVC和KVO 一、KVC操作 OC...

jiangwei0910410003 ⋅ 2014/12/13 ⋅ 0

KVC与KVO的相关文章

1、KVC与KVO的理解:http://magicalboy.com/kvcandkvo/ 2、官方KVC文档: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/KeyValueCoding/Articles/KeyValueCodin......

hejunbinlan ⋅ 2016/06/02 ⋅ 0

OC中类的扩展

类的属性写在 .h 文件里面可以被继承的子类使用 类的属性写在 .m 大括号内,只能被本类使用 读取项目文件中的文件 - (void)viewDidLoad { [super viewDidLoad]; //读取项目文件中的文件 //创...

Dumplings ⋅ 2016/03/31 ⋅ 0

消息机制,KVC,KVO

iOS 提供了一种 "同步的" 消息通知机制,观察者只要向消息中心注册, 即可接受其他对象发送来的消息,消息发送者和消息接受者两者可以互相一无所知,完全解耦。 这种消息通知机制可以应用于任...

5vinsEnt ⋅ 2016/06/12 ⋅ 0

ios技术面试题

1.Difference between shallow copy and deep copy? 浅复制 只拷贝地址 不拷贝地址指向的对象 深复制 拷贝地址 并且指向拷贝的新对象 2.What is advantage of categories? What is differenc...

AmoyAI ⋅ 2012/12/08 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

对于程序员的招聘问题,作为软件人的一些吐槽和建议

作为软件人,找工作有时候似乎挺苦逼的。 说真的,让我去掉前面这句中“似乎”二字吧。就是苦逼!很多人都曾抱怨处在招聘的一方很糟糕——我们没有任何可靠的方式来甄别会写代码并且写得好的...

老道士 ⋅ 35分钟前 ⋅ 0

HDFS原理学习

一、概述 1、 Hadoop整合了众多的文件系统,首先提供了一个高层的文件系统抽象org.apache.hadoop.fs.FileSystem。然后有各个文件系统的实现类。 2、Hadoop是JAVA编写的,不同文件系统之间的交...

cjxcloud ⋅ 39分钟前 ⋅ 0

Linux下MySQL表名不区分大小写的设置方法(抄袭别人的)

Linux下MySQL表名不区分大小写的设置方法 MySQL表名不区分大小写的设置方法 在用centox安装mysql后,把项目的数据库移植了过去,发现一些表的数据查不到,排查了一下问题,最后发现是表名的大...

随风而浮沉 ⋅ 44分钟前 ⋅ 0

ubuntu下安装宋体simsun

sudo cp simsun.ttc /usr/share/fonts cd /usr/share/fonts sudo chmod 644 simsun.ttc 更新字体缓存: 代码: sudo mkfontscale 代码: sudo mkfontdir 代码: sudo fc-cache -fsv 安装chrome扩......

wangxuwei ⋅ 45分钟前 ⋅ 0

利用 ssh 传输文件

Linux 下一般可以用 scp 命令通过 ssh 传送文件: #把服务器上的 /home/user/a.txt 发送到本机的 /var/www/local_dir 目录下scp username@servername:/home/user/a.txt /var/www/local_dir...

大灰狼时间 ⋅ 55分钟前 ⋅ 0

web3j教程:android和java程序员如何使用web3j开发区块链以太坊

如何使用web3j为Java应用或Android App增加以太坊区块链支持,本教程内容即涉及以太坊中的核心概念,例如账户管理包括账户的创建、钱包创建、交易转账,交易与状态、智能合约开发与交互、过滤...

智能合约 ⋅ 今天 ⋅ 0

web3j开发java或android以太坊智能合约快速入门

web3j简介 web3j是一个轻量级、高度模块化、响应式、类型安全的Java和Android类库提供丰富API,用于处理以太坊智能合约及与以太坊网络上的客户端(节点)进行集成。 可以通过它进行以太坊区块链...

笔阁 ⋅ 今天 ⋅ 0

一起读书《深入浅出nodejs》-异步I/O

异步I/O “异步”这个名词其实很早就诞生了,但它大规模流行却是在Web 2.0浪潮中,它伴随着AJAX的第一个A(Asynchronous)席卷了Web。 为什么要异步I/O 关于异步I/O为何在Node里如此重要,这与...

小草先森 ⋅ 今天 ⋅ 0

JVM各种问题

1、如果启动什么都不设,会怎样? 先来看一个命令 [root@localhost bin]# java -XX:+PrintCommandLineFlags -version -XX:InitialHeapSize=29899008 -XX:MaxHeapSize=478384128 -XX:+PrintCo......

算法之名 ⋅ 今天 ⋅ 0

SAS笔记-宏2

宏是一种文本,一般来说其编译是在程序执行之前。 宏变量的创建 %let语句 %let macro_variables = text; %let是常见的宏变量建立方式,其编译就在执行前。如下例中,想要宏变量test等于数据集...

tonorth123 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部