在 Core Data 中存取 transformable 类型的数据
本文转载至 http://imenjoe.com/2015/04/10/CoreData-transformable-20150410/
在开发过程中有一个需要在 Core Data 中存取 NSDictionary 的需求,但是在 Core Data 的 attributeType 中并没有直接支持 NSDictionary 的类型,基本上就是一些 scalar 类型和 date、binary data、undefined 和 transformable 类型。
最初的想法是把 NSDictionary 转为 NSData 然后用 binary data 的类型写入到 Core Data,然后要用的时候再从 Core Data 中读出 NSData,再转回 NSDictionary,但是想想这样其实也挺麻烦的,想看看 Core Data 有没有更好的对这类需求的支持,这个方法就作为最后的手段来使用吧。于是在扫了一遍 Core Data 的 attributeType 之后就盯上了 transformable 这个类型。
在 Apple 开发者文档中对 transformable 的描述是:
The idea behind transformable attributes is that you access an attribute as a non-standard type, but behind the scenes Core Data uses an instance of NSValueTransformer to convert the attribute to and from an instance of NSData. Core Data then stores the data instance to the persistent store.
大概意思就是把上面提到的想法隐藏在了 transformable 之下,使之可以直接用 non-standard 类型(在我们的例子中就是 NSDictionary)透明地访问 Core Data 中的 attribute。整个转换和持久化的过程都由 Core Data 自动完成。
这个转换过程 Core Data 需要一个 transformer 实例来进行 non-standard 类型跟 NSData 之间的转换,这个 transformer 实例是抽象类 NSValueTransform 的实现的一个实例,这个类的作用在于把一个值转换为另一个值,这个超出了这次的讨论范围,关于 NSValueTransform 可以参考这篇文章。
使用
要使用 transformable 类型有两种方式:
如果是在 .xcdatamodeld 中编辑,在 Type 的下拉菜单中选择 transformable 类型,在这个 attribute 的属性页的 Name 文本框中填 transformer name(如果要自定义 transformer 的话才需要填,否则就使用 Core Data 默认的)
如果是用代码写的,那么调用 setAttributeType: 时传入 NSTransformableAttributeType 作为参数,然后(如果需要的话)用 setValueTransformerName: 来指定 transformer 的 name。
我使用的是第一种方式,直接选择了 attribute 的 Type 为 transformable 后就完成了。由于 value transformer name 的文本框为空,所以 Core Data 会默认使用 NSKeyedUnarchiveFromDataTransformerName 为 transformer。这个 transformer 其实就是调用 NSKeyArchiver 来对 non-standard 类型转换。整个过程大概类似于这样:
NSDictionary -> NSData
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:myDictionary];
NSData -> NSDictionary
NSDictionary *dictionary = (NSDictionary*)[NSKeyedUnarchiver unarchiverObjectWithData:data];
由于我要存取的 NSDictionary 是直接支持用 NSKeyedArchiver 来转换的,所以我没有自定义 transformer,直接用默认的 transformer 就可以满足我的需要了。
接下来就是重新生成实体类,由于我用的是 mogenerator 来生成的,要让 mogenerator 在生成的实体类中对应的 attribute 是我想要的 NSDcitionary,那么还需要在 .xcdatamodeld 中给 attribute 的 User Info 添加一项
| Key | Value | |
|---|---|---|
| attributeValueClassName | NSDictionary |

mogenerator 在生成实体类时就会从这里获取到信息,知道这个 attribute 要转换的 non-standard 是 NSDictionary,在生成的实体类中对应的 property 就会是 NSDictionary,从而使得我们可以直接以 NSDictionary 类型对这个 property 进行读写,而完全不用管底下的转换和存储的过程。
transformable 类型除了可以存取 NSDictionary 类型之外,还可以存取 UIColor、CGRect 等等类型,总之如果在 Core Data 提供的标准类型中找不到自己想要的类型,那么就可以用 transformable 来进行存取,方便快捷。
扩展
到这里其实这个问题就已经算是解决了,但是我们在上面提到了一个自定义 transformer,这里我们还可以对这个扩展一下。
上面提到了 transformable 可以存取 NSDictionary 之外的其他的 non-standard 类型,例如 Color、Image 之类的,但是如果用的时默认的 transformer 的话,那么这个 non-standard 必须实现 NSCoding 协议,才能将其实例序列化为 NSData,并且进行存取。我之所以直接用默认的 transformer 也是因为 NSDictionary 实现了 NSCoding 协议的。但并不是所有的 non-standard 类型都实现了 NSCoding 协议的,要存取这些没有实现 NSCoding 协议的类型怎么办?
这个时候就轮到自定义的 transformer 出场了,上面提到过 transformer 其实就是 NSValueTransform 抽象类的实现的一个实例,而这个类的作用就是把一个值按照指定的规则转换为另一个值,所以我们可以通过实现 NSValueTransform 来指定规则,创建实例来完成我们的自定义 transformer。
关于自定义 transformer 在 Apple 开发者文档上是这样描述的:
If you specify a custom transformer, it must transform an instance of the non-standard data type into an instance of NSData and support reverse transformation.
如果只是需要单向转换的话,那么只需要实现这几个方法即可:
+ (Class) transformedValueClass;
+ (BOOL) allowsReverseTransformation;
- (id) transformedValue:(id)value;
如果还要支持反向转换的话,那么还需要实现这个方法:
- (id)reverseTransformedValue:(id)value;
由于 Core Data 的 transformable 是需要支持双向转换的(从 non-standard type 到 NSData,还有从 NSData 到 non-standard type),所以上面的方法都要实现,下面是我根据网上两个例子( SO 和 Eugene’s blog) 改造而来的一个例子:
#import <UIKit/UIKit.h>
@interface UIImageToNSDataTransformer : NSValueTransformer
{
}
@end
#import "UIImageToNSDataTransformer.h"
@implementation UIImageToNSDataTransformer
+ (Class)transformedValueClass
{
return [NSData class];
}
+ (BOOL)allowsReverseTransformation
{
return YES;
}
- (id)transformedValue:(id)value
{
if (value == nil)
return nil;
// I pass in raw data when generating the image, save that directly to the database
if ([value isKindOfClass:[NSData class]])
return value;
return UIImagePNGRepresentation((UIImage *)value);
}
- (id)reverseTransformedValue:(id)value
{
return [UIImage imageWithData:(NSData *)value];
}
@end
实现了 NSValueTransform 后,最后要做的就是在 .xcdatamodeld 的 transformable attribute 的属性页中,把这个自定义 transformer 的类名填写在 Name 文本框中。
自定义的 transformer 还可以引申出另一个应用,就是可以用于对 entity 中某个 attribute 进行加密/解密,用自定义的加密/解密算法。单独对某个 attribute 进行加密/解密,可以避免对整个 entity 甚至是整个 database 进行加密/解密,提高了性能,降低了内存消耗。这个在 SO 这个答案中有一些说明。
(注:对于 NSValueTransform 的使用,在 Mantle 库的NSValueTransformer +MTLPredefinedTransformerAdditions、MTLValueTransformer 等类中有很好地学习例子,还有 mattt 的 TransformerKit 库也是。)
在 Core Data 中存取 transformable 类型的数据的更多相关文章
- “System.Data”中不存在类型或命名空间名称“TypedTableBase”
错误 1 命名空间“System.Data”中不存在类型或命名空间名称“TypedTableBase”(是否缺少程序集引用?) 解决方案 因为是把强类型DataSet文件绑定报表的项目中出现的错误, ...
- ASP.NET#命名空间"System.Data"中不存在类型或命名空间名称"Linq"(是否缺少程序集引用?)
添加完.dbml(LINQ to SQL类文件)文件后,双击.designer.cs源文件时,发现编译器提示:命名空间"System.Data"中不存在类型或命名空间名称" ...
- iOS教程:如何使用Core Data – 预加载和引入数据
这是接着上一次<iOS教程:Core Data数据持久性存储基础教程>的后续教程,程序也会使用上一次制作完成的. 再上一个教程中,我们只做了一个数据模型,之后我们使用这个数据模型中的数据创 ...
- 向mysql中插入Date类型的数据
先看数据库表的定义 date字段为sql.date类型.我要向其中插入指定的日期和当前日期. 一.插入当前日期 思路:先获取当前系统,在将当前系统时间转换成sql类型的时间,然后插入数据库.代码如下 ...
- iOS:Core Data 中的简单ORM
我们首先在xcdatamodel文件中设计我们的数据库:例如我建立一个Data的实体,里面有一个String类型的属性name以及一个Integer类型的num: 然后选中Data,添加文件,选择NS ...
- 再看Core Data中PSC陷入死锁的问题
在<Core Data Programming Guide>文档的Concurrency with Core Data这一章节中提到了“Use Thread Confinement to ...
- SQL中的float类型的数据
问题1. 如何在SQL中默认的使用float类型的数据 SQL中想要通过计算的方式最快的得到一个float类型的数据,只需要运算的其中一个值后面加上小数点就ok. 比如 :9/2=4 但是 :9/2 ...
- 如何在R中导入不同类型的数据
这个表格是我在datacamp学习R导入文件的课程的归纳 遇到的问题及解决方法(环境: Rv3.2.5,win7,32位) 1. 使用gdata中的read.xls时提示找不到Perl路径 >l ...
- django -- model中只有Field类型的数据才能成为数据库中的列
一.model的定义: from django.db import models # Create your models here. class Person(models.Model): firs ...
随机推荐
- 使用Selenium来抓取动态加载的页面
原文:http://my.oschina.net/flashsword/blog/147334?p=1 一般的爬虫都是直接使用http协议,下载指定url的html内容,并对内容进行分析和抽取.在我写 ...
- 第三百七十节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现搜索结果分页
第三百七十节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现搜索结果分页 逻辑处理函数 计算搜索耗时 在开始搜索前:start_time ...
- Java如何显示不同语言的时间?
在Java中,如何显示不同语言的时间? 此示例使用DateFormat类以中文语言显示时间. package com.yiibai; import java.text.DateFormat; impo ...
- Python:数组、队列及堆栈的使用(list用法)--转
Python编程中数组.队列及堆栈用于保存一组数据或对象的序列,元素可以是各种类型混合在一起,定义格式为[元素,元素,……,元素],用变量[位置]即可取出相应的元素,其中“位置”是从零开始计算. 数组 ...
- 查看eclipse版本信息
http://www.cnblogs.com/caiyuanzai/archive/2013/01/11/2855796.html 如果要查询eclipse数字版本号的话,可按如下进行操作: 1. 找 ...
- Linux网卡eth0变成eth1修改方法
由于换了主板,集成网卡mac地址变了,70-persistent-net.rules中仍然保留了老网卡的内容,新网卡则被识别为eth1. 将表示老网卡的行注释掉,然后将表示新网卡的行中eth1改成et ...
- 《经久不衰的Spring框架:@ResponseBody 中文乱码》(转)
转载自:http://www.cnblogs.com/shanrengo/p/6429291.html 问题背景 本文并不是介绍@ResponseBody注解,也不是中文乱码问题的大汇总笔记,这些网上 ...
- MySQL存储引擎Innodb和MyISAM对比总结
Innodb引擎 InnoDB是一个事务型的存储引擎,设计目标是处理大数量数据时提供高性能的服务,它在运行时会在内存中建立缓冲池,用于缓冲数据和索引. Innodb引擎优点 1.支持事务处理.ACID ...
- iOS :ViewDidAppear
进入一个 UIViewController 会调用它的三个方法,分别是 viewDidLoad, viewWillAppear, viewDidAppear. 如每个方法的名字一样,在不同的方法中要处 ...
- 通过tarball形式安装HBASE Cluster(CDH5.0.2)——配置分布式集群中的YARN ResourceManager 的HA
<?xml version="1.0"?> <!-- Licensed under the Apache License, Version 2.0 (the &q ...