Objective-C中的KVC
Posted __Sunshine_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Objective-C中的KVC相关的知识,希望对你有一定的参考价值。
本文主要介绍KVC的以下几部分:
1、概念
KVC, 即 Key-Value Coding(有翻译成键值编码)
是通过字符串来识别属性名,来间接访问对象属性的机制,与调用setter、getter方法的直接访问不同。
作用是简化代码,可以动态地设置和读取属性。
主要的KVO方法声明在OC的非正式协议 NSKeyValueCoding 中
@interface NSObject(NSKeyValueCoding)
KVC中用来划分属性的一些术语:
attribute表示只有一个简单的值的属性,如数值、字符串、Boolean值、NSNumber等;
对一关系(to-one relationship)表示拥有自己属性的对象,如NSView;
对多关系(to-many relationship)表示由集合组成的属性,如NSArray、NSSet。
key 与 key paths
key 是用来识别对象的属性的字符串,它可以与对象的实例变量名相同,也可以与访问方法名(setter,getter)相同。key要用ASCII编码,以小写字母开头,不能包含空格。如amount, page, name.
key paths是用来识别多层属性的,如对象person中有属性为book对象,book对象中有int型属性page,则对person对象使用的字符串应该用“.”分隔属性名,如:book.page
2、基本用法
1)用KVC获取属性值:
- (id)valueForKey:(NSString *)key;
// 依字符串key来获取对应的属性的值,返回值是能表示该属性值的id
- (id)valueForKeyPath:(NSString *)keyPath;
// 依字符串keyPath来获取符合路径的属性(多层),返回值是能表示该属性值的id
- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;
// 获取一组属性值。keys为要获取的多个属性名的数组。返回值是个字典,其键是keys中的元素,其值是对应的值。
例子如下:
// Book.h
#import <Foundation/Foundation.h>
#import "Author.h"
@interface Book : NSObject //声明Book类代表书
@property (nonatomic, assign) int page; //属性,表页码
@property (nonatomic, assign) double price; //属性,表价格
@property (nonatomic, strong) Author *author;//属性,表作者
@end
// Book.m
#import "Book.h"
@implementation Book
@end
// Author.h
#import <Foundation/Foundation.h>
@interface Author : NSObject //声明Author类表作者
@property (nonatomic, strong) NSString *name;//属性,表名字
@end
// Author.m
#import "Author.h"
@implementation Author
@end
// main.m
#import <Foundation/Foundation.h>
#import "Book.h"
#import "Author.h"
int main(int argc, const char * argv[])
@autoreleasepool
// 创建对象
Book *book = [Book new];
Author *author = [Author new];
// 对象属性赋值
author.name = @"wangxiaobo";
book.page = 200;
book.price = 58.5;
book.author = author;
// 用 KVC 获取属性值
// (1)获取 book 的 page 值
int thePage = [[book valueForKey:@"page"] intValue];
// thePage == 200
// (2)获取 book 的 author 对象的 name 值
NSString *theName = [book valueForKeyPath:@"author.name"];
// theName == @"wangxiaobo"
// (3)获取多个属性值,设置要获取的一组属性:
NSArray *atrArr = @[@"page", @"price"];
// 获取到一组属性的值,返回类型为字典,属性名为key,属性值为value
NSDictionary *artDic = [book dictionaryWithValuesForKeys:atrArr];
// artDic ==
// page = 200;
// price = "58.5";
//
return 0;
示意图如下:
2)用KVC设置对象的属性值
- (void)setValue:(id)value forKey:(NSString *)key;
// 将对象的key属性设置为value(注意value的类型是id)
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
// 将对象的keyPath路径下的属性设置为value
- (void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues;
// 用字典keyedValues对对象的多个属性赋值,用其键来匹配属性名,用其值来设置属性的值
// 对于每个键值对,都会默认调用setValue: value forKey:方法
例子如下:
//(接上面的代码)
// 用 KVC 设置属性值
// 设置属性 page 的值
[book setValue:@250 forKey:@"page"];
// book.page == 250
// 用 KVC 设置 book 的 author 的 name 的值
[book setValue:@"luxun" forKeyPath:@"author.name"];
// book.author.name == @"luxun"
// 设置多个属性的值
// 创建一个字典,key为要设置的属性名,value为对应的值
NSDictionary *setDic = @@"page":@300, @"price":@65.5;
// 用字典去设置多个属性的值
[book setValuesForKeysWithDictionary:setDic];
// book.page == 300
// book.price == 65.5
KVC与点语法之间没什么必然的联系。用不用点语法,都可以考虑用KVC;用不用KVC,都可以考虑用点语法。在KVC中,用key路径来限定元素,返回值不会是基本数据类型;在点语法中,调用的是访问器方法(setter, getter),返回值可以是基本数据类型。
3、当属性为对多属性(如集合NSArray,NSSet等)
1)虽然在获取和设置过程中,如果属性不是对象类型的(如基本数据类型,结构体等),上面方法可以自动的包装和拆包将非对象类型与对象类型进行转换,但当想要把属性值设置为零值时,要重写调用的方法setNilValueForKey:,否则出错。重写如下:
// Book.m
#import "Book.h"
@implementation Book
// 重写 setNilValueForKey:方法
-(void)setNilValueForKey:(NSString *)key
// 对要设置为nil的属性名作判断
if ([key isEqualToString:@"page"])
// 如果是属性 page,则用 @0 代表要设置的值
[self setValue:@0 forKey:@"page"];
else if ([key isEqualToString:@"price"])
// 如果是属性 price,则用 @0.0 代表要设置的值
[self setValue:@0.0 forKey:@"price"];
else
// 如果要设置的属性是对象,则调用父类的setNilValueForKey:方法
[super setNilValueForKey:key];
@end
2)setter、getter 方法最好只用在集合对象上,对于集合中的元素,应该实现另外一些方法来进行访问。如:
-countOf<Key>
应实现的方法。作用类似于 NSArray 的 count 方法。
-objectIn<Key>AtIndex: 或者-<key>AtIndexs:
应实现两者之一,分别类似于 NSArray 的方法 objectAtIndex: 和 objectsAtIndexes:。
-get<Key>:range:
可实现可不实现,类似于 NSArray 的 getObjects:range: 方法。
例子如下:
// Book.h
#import <Foundation/Foundation.h>
#import "Author.h"
@interface Book : NSObject //声明Book类代表书
@property (nonatomic, strong) NSArray *sellPlace; //属性,是数组,表书的销售城市
//声明countOfSellPlace方法
-(NSUInteger)countOfSellPlace;
//声明由下标获取集合属性中的元素的方法
-(id)objectInSellPlaceAtIndex:(NSUInteger)index;
@end
// Book.m
#import "Book.h"
@implementation Book
//实现countOfSellPlace方法
-(NSUInteger)countOfSellPlace
//调用 NSArray 的count方法,将结果返回
return [self.sellPlace count];
//实现方法,作用是通过下标访问集合属性中的元素
-(id)objectInSellPlaceAtIndex:(NSUInteger)index
//调用 NSArray 的 objectAtIndex:方法,将结果返回
return [self.sellPlace objectAtIndex:index];
@end
// main.m
#import <Foundation/Foundation.h>
#import "Book.h"
int main(int argc, const char * argv[])
@autoreleasepool
// 创建对象
Book *book = [Book new];
//设置书的销售城市
NSArray *sellPlace = @[@"BJ", @"SH", @"GZ"];
book.sellPlace = sellPlace;
// 调用方法,获取 book 对象中数组 sellPlace 的元素个数
NSUInteger cou = [book countOfSellPlace];
// cou == 3
// 调用方法,通过下标获取 book 对象中数组 sellPlace 的元素
NSString *aSellPlace = [book objectInSellPlaceAtIndex:1];
// aSellPlace == @"SH"
return 0;
3)如果集合是可变的,还应实现一些方法使得可以对集合的元素进行操作。如:
• -insertObject:in<Key>AtIndex: 或者 -insert<Key>:atIndexes:
应该实现这两者之一。类似于 NSMutableArray 的方法 insertObject:atIndex: 和 insertObjects:atIndexes:。
• -removeObjectFrom<Key>AtIndex:或者-remove<Key>AtIndexes:
应该实现这两者之一。类似于 NSMutableArray 的方法 removeObjectAtIndex: 和 removeObjectsAtIndexes:。
• -replaceObjectIn<Key>AtIndex:withObject: 或者 -replace<Key>AtIndexes:with<Key>:
可实现可不实现。 Implement if benchmarking indicates that performance is an issue.
例子如下:
// Author.h
#import <Foundation/Foundation.h>
@interface Author : NSObject //声明Author类表作者
@property (nonatomic, strong) NSString *name;//属性,表名字
@end
// Author.m
#import "Author.h"
@implementation Author
@end
// Book.h
#import <Foundation/Foundation.h>
#import "Author.h"
@interface Book : NSObject //声明Book类代表书
@property (nonatomic, strong) NSMutableArray *authors; //可变数组,用来存储多个作者
//声明给可变对多属性插入元素的方法
-(void)insertObject:(Author *)object inAuthorsAtIndex:(NSUInteger)index;
//声明对可变对多属性删除元素的方法
-(void)removeObjectFromAuthorsAtIndex:(NSUInteger)index;
@end
// Book.m
#import "Book.h"
@implementation Book
//实现给可变对多属性插入元素的方法
-(void)insertObject:(Author *)object inAuthorsAtIndex:(NSUInteger)index
[self.authors insertObject:object atIndex:index];
//实现对可变对多属性删除元素的方法
-(void)removeObjectFromAuthorsAtIndex:(NSUInteger)index
[self.authors removeObjectAtIndex:index];
@end
// main.m
#import <Foundation/Foundation.h>
#import "Book.h"
#import "Author.h"
int main(int argc, const char * argv[])
@autoreleasepool
// 创建book对象
Book *book = [Book new];
// (1)插入元素到对象的可变数组属性中:
// 创建两个Author对象
Author *author = [Author new];
Author *author_2 = [Author new];
author.name = @"wangxiaobo";
author_2.name = @"guojingming";
// 创建可变数组authors,里面有一个元素:author对象(用来表示第一个作者)
NSMutableArray *authors = [NSMutableArray arrayWithObject:author];
// 此时:authors == @[author]
// 用 KVC 将可变数组 authors 赋值给 book 的 NSMutableArray 型属性
[book setValue:authors forKey:@"authors"];
// 此时:book.authors == @[author]
// 调用已实现的插入方法将 author_2 对象插入book 的 NSMutableArray 型属性中下标为1的位置
[book insertObject:author_2 inAuthorsAtIndex:1];
// 此时:book.authors == @[author, author_2]
// 原可变数组 authors 也等于 @[author, author_2]
// (2)从对象的可变数组属性中删除元素:
// 删除 book 对象中的可变数组 authors 的下标为1的元素
[book removeObjectFromAuthorsAtIndex:1];
// 此时:book.authors == @[author]
return 0;
4、集合操作符(Collection Operators)
当对象属性是集合,集合操作符可以通过键路径记号(key path notation)与一个操作符,来对集合的元素进行一些操作。
集合操作符是一种特殊的键路径,是作为参数传给 valueForKeyPath: 方法。其中的操作符是以“@”开头的,操作符左边的是对象中的、用来操作的一个 array 或者 set,而操作符右边的是被操作符使用的集合的属性。格式如下:
操作符分类:
(1)简单集合操作符:返回字符串、数字、日期等,应用NSNumber型对象接收。
(2)对象操作符:返回一个 NSArray 的对象
(3)数组或set操作符:返回一个 NSArray 或 NSSet 的对象。
具体使用例子和说明如下:
// Book.h
#import <Foundation/Foundation.h>
#import "Author.h"
@interface Book : NSObject //声明Book类代表书
@property (nonatomic, strong) NSMutableArray *authors; //可变数组,用来存储多个作者
@property (nonatomic, strong) NSArray *authors_2;
@end
// Book.m
#import "Book.h"
@implementation Book
@end
// Author.h
#import <Foundation/Foundation.h>
@interface Author : NSObject //声明Author类表作者
@property (nonatomic, strong) NSString *name;//属性,表作者的名字
@property (nonatomic, strong) NSNumber *age; //属性,表作者的年龄
@property int year;
@end
// Author.m
#import "Author.h"
@implementation Author
@end
// main.m
#import <Foundation/Foundation.h>
#import "Book.h"
#import "Author.h"
int main(int argc, const char * argv[])
@autoreleasepool
// 创建对象
Book *book = [Book new];
Author *author = [Author new];
Author *author_2 = [Author new];
Author *author_3 = [Author new];
// 对象属性赋值
author.name = @"wangxiaobo";
author.age = @20;
author_2.name = @"guojingming";
author_2.age = @30;
author_3.name = @"guojingming";
author_3.age = @22;
// 创建一个数组,其元素是Author类的对象
NSArray *authors_2 = @[author, author_2, author_3];
// 把数组 authors_2 赋值给 book 的 authors_2 属性
[book setValue:authors_2 forKey:@"authors_2"];
// 集合操作符:
// 1、简单集合操作符
// (1) @avg:取键路径指定的属性、操作符右边的的值的平均值(用于一个array或者set)
NSNumber *authorsAveAgeInB = [book valueForKeyPath:@"authors_2.@avg.age"];
// authorsAveAgeInB == @"24",表示所有作者的年龄平均值为24
// (2) @count:获得左边键路径的对象个数,省略右边的键路径
NSNumber *numOfAuthor = [book valueForKeyPath:@"authors_2.@count"];
// numOfAuthor == @3L,表示有三个作者
// (3) @max:将键路径指定的属性、操作符右边的的值进行比较,取最大值返回
NSNumber *oldest = [book valueForKeyPath:@"authors_2.@max.age"];
// oldest == @30,表示书的作者中年纪最大的是30岁
// (4) @min:将键路径指定的属性、操作符右边的的值进行比较,取最小值返回
NSNumber *youngest = [book valueForKeyPath:@"authors_2.@min.age"];
// youngest == @20,表示书的作者中年纪最小的是20岁
// (5) @sum:将键路径指定的属性、操作符右边的的值进行求和,返回和的值
NSNumber *sumAge = [book valueForKeyPath:@"authors_2.@sum.age"];
// youngest == @50,表示书的所有作者的年龄之和为72岁
// 2、对象操作符
// (1) @distinctUnionOfObjects:获得指定属性的所有值组成数组,然后有相同值的只保留一个
NSArray *aut_name_DU = [book valueForKeyPath:@"authors_2.@distinctUnionOfObjects.name"];
// aut_name_DU == (
// luxun,
// guojingming
// )
// 即 书的所有作者名有@[@"luxun", @"guojingming", @"guojingming"],后面两个元素相同,删掉一个。
// (2) @unionOfObjects:获得指定属性的所有值组成数组,即使有相同的也全部保留
NSArray *aut_name_U = [book valueForKeyPath:@"authors_2.@unionOfObjects.name"];
// aut_name_U = (
// luxun,
// guojingming,
// guojingming
// )
// 后面两个元素相同也都保留
// 3、Array 和 Set 操作符
// 用在集合嵌套的情况中,即对象的属性是个集合,集合的元素又是个集合
// 再创建两个 Author 的对象,组成第二个数组
Author *author_4 = [Author new];
author_4.name = @"luxun";
Author *author_5 = [Author new];
author_5.name = @"bingxin";
// 创建一个数组,其元素是Author类的对象
NSArray *authors_3 = @[author_4, author_5];
book.authors = [NSMutableArray array];
[book.authors addObject:authors_2];
[book.authors addObject:authors_3];
// 此时 book.authors 可变数组中有两个元素,每个元素又分别是一个数组
// (1) @distinctUnionOfArrays:取指定属性中所有的值,若有相同的则只保留一个
NSArray *aut_name_DU_2 = [book valueForKeyPath:@"authors.@distinctUnionOfArrays.name"];
// aut_name_DU_2 ==
// (
// bingxin,
// luxun,
// guojingming
// )
// 对比 @distinctUnionOfObjects:只看数组的元素(此处元素是一个数组)
NSArray *aut_name_DU_2_2 = [book valueForKeyPath:@"authors.@distinctUnionOfObjects.name"];
// aut_name_DU_2_2 ==
// (
// (
// luxun,
// guojingming,
// guojingming
// ),
// (
// luxun,
// bingxin
// )
// )
// (2) @unionOfArrays:取指定属性中所有的值,若有相同的则只保留一个
NSArray *aut_name_U_2 = [book valueForKeyPath:@"authors.@unionOfArrays.name"];
// aut_name_U_2 ==
// (
// luxun,
// guojingming,
// guojingming,
// luxun,
// bingxin
// )
// (3) @distinctUnionOfSets:与@distinctUnionOfArray 类似,但是是对 NSSet 嵌套的操作,返回值类型是 NSSet
return 0;
小结:
(1)可见 KVC 的键统一是字符串,而值不能是基本数据类型
(2)相比一般的setter、getter方法,用KVC来访问对象的属性虽然不太简便,但可以动态设置、动态取值、高度规范化的访问,功能也比较强大,可以获取任何对象的任何属性(包括@private的)的值
(本文仍不能涵盖KVC的所有重要用法及其KVO的用法,待续)
以上是关于Objective-C中的KVC的主要内容,如果未能解决你的问题,请参考以下文章