本文转载至 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 类型有两种方式:

  1. 如果是在 .xcdatamodeld 中编辑,在 Type 的下拉菜单中选择 transformable 类型,在这个 attribute 的属性页的 Name 文本框中填 transformer name(如果要自定义 transformer 的话才需要填,否则就使用 Core Data 默认的)

  2. 如果是用代码写的,那么调用 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 类型的数据的更多相关文章

  1. “System.Data”中不存在类型或命名空间名称“TypedTableBase”

    错误 1 命名空间“System.Data”中不存在类型或命名空间名称“TypedTableBase”(是否缺少程序集引用?)  解决方案 因为是把强类型DataSet文件绑定报表的项目中出现的错误, ...

  2. ASP.NET#命名空间"System.Data"中不存在类型或命名空间名称"Linq"(是否缺少程序集引用?)

    添加完.dbml(LINQ to SQL类文件)文件后,双击.designer.cs源文件时,发现编译器提示:命名空间"System.Data"中不存在类型或命名空间名称" ...

  3. iOS教程:如何使用Core Data – 预加载和引入数据

    这是接着上一次<iOS教程:Core Data数据持久性存储基础教程>的后续教程,程序也会使用上一次制作完成的. 再上一个教程中,我们只做了一个数据模型,之后我们使用这个数据模型中的数据创 ...

  4. 向mysql中插入Date类型的数据

    先看数据库表的定义 date字段为sql.date类型.我要向其中插入指定的日期和当前日期. 一.插入当前日期 思路:先获取当前系统,在将当前系统时间转换成sql类型的时间,然后插入数据库.代码如下 ...

  5. iOS:Core Data 中的简单ORM

    我们首先在xcdatamodel文件中设计我们的数据库:例如我建立一个Data的实体,里面有一个String类型的属性name以及一个Integer类型的num: 然后选中Data,添加文件,选择NS ...

  6. 再看Core Data中PSC陷入死锁的问题

    在<Core Data Programming Guide>文档的Concurrency with Core Data这一章节中提到了“Use Thread Confinement to ...

  7. SQL中的float类型的数据

    问题1.  如何在SQL中默认的使用float类型的数据 SQL中想要通过计算的方式最快的得到一个float类型的数据,只需要运算的其中一个值后面加上小数点就ok. 比如 :9/2=4 但是 :9/2 ...

  8. 如何在R中导入不同类型的数据

    这个表格是我在datacamp学习R导入文件的课程的归纳 遇到的问题及解决方法(环境: Rv3.2.5,win7,32位) 1. 使用gdata中的read.xls时提示找不到Perl路径 >l ...

  9. django -- model中只有Field类型的数据才能成为数据库中的列

    一.model的定义: from django.db import models # Create your models here. class Person(models.Model): firs ...

随机推荐

  1. Ext3.4实现增删查改(form版)

    var TaskPolicyStore = new Ext.data.JsonStore( {    autoLoad : false,    url : 'PolicyServlet?method= ...

  2. 【转】【Nginx】Nginx 入门教程 + 常用配置解析

    == Nginx介绍和安装 == Nginx是一个自由.开源.高性能及轻量级的HTTP服务器及反转代理服务器, 其性能与IMAP/POP3代理服务器相当.Nginx以其高性能.稳定.功能丰富.配置简单 ...

  3. (笔记)Mysql命令select from:查询表中的数据(记录)

    select from命令用来查询表中的数据. 1) 查询所有行命令格式: select <字段1, 字段2, ...> from < 表名 > where < 表达式 ...

  4. Failed to import package with error: Couldn't decompress package的解决方案

    问题的出现 在Unity中导入一个Package文件,出现以下出错信息 解决方案 遇到这样的报错信息,一般就是要将该文件包的存储路径设置为英文,因为Unity不识别中文路径:其次对文件包的命名中间应没 ...

  5. Java如何使用线程解决死锁?

    在Java编程中,如何使用线程解决死锁? 以下示例演示如何使用线程的概念解决死锁问题. // from W w w .Y I I b AI.c o M package com.yiibai; impo ...

  6. MyBatis SqlSessionDaoSupport实例

    在前面的章节中,我们已经讲到了基本的 mybatis 操作,但都是基于 mapper 隐射操作的,在 mybatis3 中这个 mapper 接口貌似充当了以前在ibatis 2中的 DAO 层的作用 ...

  7. Android Notification和权限机制探讨

    近期为了在部门内做一次小型的技术分享.深入了解了一下Notification的实现原理.以及android的权限机制.在此做个记录.文章可能比較长,没耐心的话就直接看题纲吧. 先看一下以下两张图 图一 ...

  8. Linux网卡eth0变成eth1修改方法

    由于换了主板,集成网卡mac地址变了,70-persistent-net.rules中仍然保留了老网卡的内容,新网卡则被识别为eth1. 将表示老网卡的行注释掉,然后将表示新网卡的行中eth1改成et ...

  9. int[,] 和 int[][] 有什么区别

    int[,] 是二维数组,它就是传统意义上 n x m 的表,和 C++ 里的 int[][] 是一个意思. int[][] 是交错数组,与 C++ 里的 int[][] 不同.它其实是一个 int[ ...

  10. Java写 插入 选择 冒泡 快排

    /** * Created by wushuang on 2014/11/19. */ public class SortTest { @Test public void mainTest() { i ...