本文转载至 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. Java如何以(MMM)格式显示一个月份的名称?

    JAVA中,如何以(MMM)格式显示一个月份的名称? 此示例显示如何使用Calender类的Calender.getInstance()方法和Formatter类的fmt.format()方法来显示( ...

  2. HttpWebRequest.ReadWriteTimeout 属性

    获取或设置写入或读取流时的超时. 属性值在写入超时或读取超时之前的毫秒数.默认值为 300,000 毫秒(5 分钟). 备注 在写入由 GetRequestStream 方法返回的流时,或在读取由 G ...

  3. 嵌入式开发之makefile---交叉编译静态库和动态库的生成和调用

    c和cpp 混合的动态库生成: $(LIBSO): $(COBJS) $(CPPOBJS) $(CPP) -shared -o $@ $^ $(LIBS) ////////////////////// ...

  4. CI框架 -- 密码哈希

    哈希算法是一个单向函数.它可以将任何大小的数据转化为定长的“指纹”,并且无法被反向计算 依赖性 crypt() 函数需支持 CRYPT_BLOWFISH 常量 PASSWORD_BCRYPT PASS ...

  5. C# 判断一个字符串是否为url

    /// <summary> /// 判断一个字符串是否为url /// </summary> /// <param name="str">< ...

  6. 令人惊叹的HTML5动画及源码分析下载

    HTML5非常酷,利用HTML5制作动画简直让我们忘记了这世界上还有flash的存在.今天我们要分享的一些HTML5动画都还不错,有些动画设计还是挺别出心裁的.另外,每一款HTML5动画都提供源代码下 ...

  7. java提取url里的域名

    使用java标准类库java.net.URL java.net.URL url = new java.net.URL("http://blog.csdn.net/xxx.png") ...

  8. Java华氏转摄氏

    package test; import java.util.Scanner; public class temperature { public static void main(String[] ...

  9. 简单理解AOP(面向切面编程)

    AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. AOP与OOP是面向不同领域的两种设计思想. ...

  10. [转]Linux服务器上11种网络连接状态 和 TCP三次握手/四次挥手详解

    一.Linux服务器上11种网络连接状态: 图:TCP的状态机 通常情况下:一个正常的TCP连接,都会有三个阶段:1.TCP三次握手;2.数据传送;3.TCP四次挥手. 注:以下说明最好能结合”图:T ...