IOS 之 KVC 数据模型应用

  引言: IOS为我们提供了KVC,KVO的实现机制,但在一般的APP开发中,应用这种技术的比较少,但是这种技术在多数据型项目处理中可以起到很好的简化代码、APP整体架构的作用,本篇中只介绍KVC的在开发项目中的应用,以及相关发散性技术。

  简单介绍一下KVC: Key-Value Coding,它是一种使用字符串标识符,间接访问对象属性的机制,在某种程度上跟map的关系匪浅,更深入点使用反射机制对类成员变量进行操作。

  先看使用方法:

  1、先定义一个类。

 1 /*!
 2  @Description  RSS源分类目录实体
 3  @see      Entity.h
 4  @author   sherwin.chen
 5  @version  1.0.0
 6  */
 7 @interface RSSCategoryEntity : Entity
 8 @property (nonatomic, copy) NSString *strName;      //分类名
 9 @property (nonatomic, copy) NSString *strIconName;  //分类icon图标
10 @property (nonatomic, assign) NSInteger nRssNum;    //rss条数目
11 @end

     2.外部使用RSSCategoryEntity实体类

 1     RSSCategoryEntity *rss = [[RSSCategoryEntity alloc] init];
 2     
 3     //key use
 4     [rss setValue:@"sherwin.chen" forKey:@"strName"];
 5     [rss setValue:@"iRSS.icon"    forKey:@"strIconName"];
 6     
 7     NSString* strRSSName = [rss valueForKey:@"strName"];
 8     NSString* strIconName= [rss valueForKey:@"strIconName"];
 9     
10     //normal use
11     strRSSName = rss.strName;
12     strIconName= rss.strIconName;

  显然,在一般的项目中,使用KVC操作会使代码冗长,故这种技术在IOS开发中一般很少使用,但是好的技术得用在好的地方。通常,在我们与服务端API接口进行数据交换中,在得到服务端返回的模型化对象 JSON串中,常用的处理就是使用 objectForKey: 来初使化自定义模块。

1     NewsEntity *newsEnt = self;
2     newsEnt.strId                = [dict objectForKey:BASE_FILDS_ID];
3     newsEnt.strTitle             = [dict objectForKey:NEWS_FILDS_Title];
4     newsEnt.arImageUrl           = [dict objectForKey:NEWS_FILDS_ImageUrl];
5     newsEnt.strCategoryID        = [dict objectForKey:NEWS_FILDS_CategoryID];

但有了 KVC,我们可以优雅的将JSON串转换为模型对象。

假设服务器返回的JOSN串为:

1 {
2         "id":"20140308",
3         "name":"IOS Dev",
4         "iconName":"icon.png"
5         "date":1394254208
6 }

为此,我们可以设计此JSON对应有模型:

 1 @interface RSSEntity:Entity
 2 @property (nonatomic, copy) NSString *itemID;
 3 @property (nonatomic, copy) NSString *name;
 4 @property (nonatomic, copy) NSString *iconName;
 5 @property (nonatomic, assign) int unixDate;
 6 
 7 //模型初使化接口
 8 -(id) initWithDictionary:(NSDictionary*) jsonDict;
 9 
10 @end
 1 @implementation RSSEntity
 2 -(id)initWithDictionary:(NSDictionary *)jsonDict
 3 {
 4     if (self=[super init]) {
 5         [self init];
 6         [self setValuesForKeysWithDictionary:jsonDict];
 7     }
 8     return self;
 9 }
10 
11 -(void)setValue:(id)value forUndefinedKey:(NSString *)key
12 {
13     if ([key isEqualToString:@"id"]) {
14         self.itemID = value;
15     }
16     else if ([key isEqualToString:@"date"]) {
17         self.unixDate = [value integerValue];
18     }
19     else
20     {
21         NSLog(@"RSSEntity: Undefined Key: %@", key);
22         [super setValue:value forKeyPath:key];
23     }
24     return;
25 }
26 @end

     这里最重要的部分就是对  setValuesForKeysWithDictionary:方法的调用,这个方法是 Objective-C KVC的技术重点,用来匹配类中与字典的键同名的属性,并将字典的值赋给该属性。

     将JSON串对象化成 NSDictionary后,传给 setValuesForKeysWithDictionary:方法时,它会发送下面这消息(同时发送对应的值):setItemID, setName,setIconName,setUnixDate.所以对应的模型中,你只需要使用@property进行属性声明就可以了。

     但是实际Coding设计中,与技术方案总有出入,JSON串中有 Key值为 "id",但在Objective-C的世界中, id是类属性,属于关键字,不能用来做变量名,所以为了解决这问题,我们可以重写[ setValue:(id)value forUndefinedKey:(NSString *)key ],对一些不能关联的Key值进行数据转移到我们想对应的属性值上。至此,我们将模型实体所需要初使化的任务交给了它自已,这样的结构是很好的架构设计,如果后期服务端返回的JSON串字段增加,我只需在模型头文件中增加对应的属性声明就可以了。

    同时这段代码具有良好的防御性,如果服务器发送过来的JSON中的键名发生了变动,(有可能是未位程序猿手贱,又没测试), NSLog语句就会把未定义的键输出,如果因此导致你的APP Crash,等你Debug看日志的时候,你就可以去牛B的和服务端说,“兄弟,这个BUG,你看着办呗”,(和服务端调试API是我最不想干的事儿.)

   如上的设计永远满足不了我们的要求,因为现在的数据交互中,JSON串永远不止我举例这么简单,比如:

1 {
2     "id":"20140308",
3     "name":"IOS Dev",
4     "iconName":"icon.png",
5     "date":1394254208,
6     "useInfo":{"name":"sherwin.chen","mail":"chensheng12330@gmail.com"}
7 }

这就是JSON嵌套对象了,应对这种IOS API提供了人性化设计,你可以重写 setValue:forKey:方法来处理这个问题。代码说事儿:

1.既然有新的对象,那么必须先创建该对象的实体.

1 @interface UserInfoEntity : Entity
2 @property (nonatomic, retain) NSString *name;
3 @property (nonatomic, retain) NSString *mail;
4 -(id) initWithDictionary:(NSDictionary*) jsonDict;
5 @end

2.在RSSEntity模型实体中加入属性设置器.

1 @property (nonatomic, retain) UserInfoEntity *userInfo;

3.在 RSSEntity.m 中重写 setValue:forKey:方法.

 1 -(void)setValue:(id)value forKey:(NSString *)key
 2 {
 3     if ([key isEqualToString:@"userInfo"]) {
 4         self.userInfo = [[[UserInfoEntity alloc] initWithDictionary:value] autorelease];
 5     }
 6     else
 7     {
 8         [super setValue:value forKey:key];
 9     }
10 }

有了这样的结构,我们可以这样的访问RSSEntity实例化对象中的深层次对象:

1 [rss valueForKeyPath:@"userInfo.name"];
2 [rss setValue:@"sherwin.chen" forKeyPath:"userInfo.name"];

如果你觉得这样设置不爽,你可以重写 setValue:forKeyPath: 根据路径,你可以做你想做的任何事儿,在此就不多述了. 此例只是用json举例(总有此公司架构喜欢口味重,还使用XML),如果这样,你也不必关心太多,只需将XML数据解析成 NSDictionary对象,关键是 KEY要对应,如此,万事大吉。

少即是多

     网上一些资深的IOS开发者,对KVC讲述的比较多,但一般只是机制,真正在项目中应用还比较少,一些高级的技术也有它自身的弱点,如我上述的对KVC应用,也免要了要建立 JSON Key值的对应关系才能建议实体类模型,如果项目比较大,30以上的数量级的,还是很苦逼的。

    懒人总有懒办法,<objc/runtime.h> 这是Apple为我们IOS开发者提供的好东西,一些你想不到的应用可以用它所提供的API进行处理。

利用反射机制我们可以将变量名字符串,强制插入到实例化对象中去,然后,你懂的......

object_setInstanceVariable: 

object_getInstanceVariable:

这两个是好东西,有兴趣的可以去Coding实践一下,该篇文章只讲KVO.嘿嘿...

参考资料:

[IOS DOC For Apple.

IOS 6 Programming Pushing the Limits.]

转的话,标明个原地址呗: http://www.cnblogs.com/chensheng12330/p/3585489.html

原文地址:https://www.cnblogs.com/chensheng12330/p/3585489.html