本文讲的是「最简单」的 Core Data 上手指南,
  • 原文地址:The Easiest Core Data
  • 原文作者:Alberto De Bortoli
  • 译文出自:掘金翻译计划
  • 译者:Zheaoli
  • 校对者:Kulbear, cbangchen

在过去的几个月里,我花费了大量的时间在研究 Core Data 之上,我得去处理一个使用了很多陈旧的代码,糟糕的 Core Data 以及违反了多线程安全的项目。讲真,Core Data 学习起来非常的困难,在学习 Core Data 的时候,你肯定会感到迷惑和一种深深的挫败感。正是因为这些原因,我决定给出一种超级简单的解决方案。这个方案的特点就是简洁,线程安全,非常易于使用,这个方案能满足你大部分对于 Core Data 的需求。在经过若干次的迭代后,我所设计的方案最终成为一个成熟的方案。

OK,女士们,先生们,现在请允许我隆重向您介绍 Skiathos 和 Skopelos。其中 Skiathos 是基于Objective-C 所开发的,而 Skopelos 则基于 Swift 所开发的。这两个框架的名字来源于希腊的两个岛,在这里,我渡过了2016年的夏天,同时,在这里完成了两个框架的编写工作。

写在前面的话

整个项目的目的就是能够让您以及其简便的方式在您的 App 中引入 Core Data。

我们将从如下几个方面来进行一个介绍:

  • CoreDataStack
  • AppStateReactor
  • DALService (Data Access Layer)

CoreDataStack

如果你有过使用 Core Data 的经验,那么你应该知道创建一个堆栈是一个充满陷阱的过程。这个组件是用于创建堆栈(用于管理 Obejct Context ),具体的设计说明可以参看 Marcus Zarra 所写的这篇文章。

其中一个和 Magical Record 或者其余第三方插件不同的是,整个存储过程都是在一个方向上发起的,可能是从某个子节点向下或者向上传递来进行持久化储存。其余的组件允许你创建以 private context 作为父节点的子节点,这将会导致 main context 不能被更新,同时只能通过通知的方式来进行合并更新。main context 是相对固定的并与 UI 进行了绑定:这样较为简单的方式可以帮助开发者更好的去完成一个 APP 的开发。

AppStateReactor

唔,其实你可以忽略这一段。这个组件属于 CoreDataStack ,在 App 切换至后台,失去节点,或者即将退出时,它负责监视相对应的修改,并把其保存。

DALService (Data Access Layer) / (Skiathos/Skopelos)

如果你拥有使用 Core Data 的经验,那么你也应该知道,我们大部分操作都是重复的,我们经常在一个 context 中调用 performBlock:/performBlockAndWait: 函数,而这个 Context 提供了一个最终会调用save: 作为最终语句的 block 。数据库的所有操作都是基于 API 中所提供的 read: 和 write: :这两个协议提供了 CQRS (命令和查询分离) 的实现。用于读取的代码块将在主体中进行运行(因为这被认为是一个已确定的单个资源)。用于写入的代码块将会在一个子线程中运行,这样可以保证实时的进行数据储存,变化的数据将会在不会阻塞主线程的情况下通过异步的方式进行储存。write:completion:方法将会程序运行完后来对数据的更改进行持久化储存。

换句话说,写入的数据在 main managed object context 和最后持久化过程中都会保证其一致性。在 主要管理对象的 context 中,相应的数据也能保证其可用性。

Skiathos/Skopelos 是 DALService 的子类, 这样可以给这个组件一个比较好听的名字。

使用介绍

在使用这一系列组件之前,你首先需要创建一个类型为 Skiathos 的属性,然后以下面这种方式去初始化它:

1
2
3
self.skiathos = [Skiathos setupInMemoryStackWithDataModelFileName:@"<#datamodelfilename>"];
// or
self.skiathos = [Skiathos setupSqliteStackWithDataModelFileName:@"<#datamodelfilename>"];

在使用 Skopelos 时,代码如下所示:

1
2
3
self.skopelos = SkopelosClient(inMemoryStack: "<#datamodelfilename>")
// or
self.skopelos = SkopelosClient(sqliteStack: "<#datamodelfilename>")

你可以通过使用依赖注入的方式来在应用的其余地方使用这些对象。不得不说,为 Core Data 栈上的不同对象创建单例是一种很不错的做法。当然,不断的创建实例的开销是十分巨大的。通常来讲,我们不是很推荐使用单例模式。单例模式的测试性不强,在使用过程中,使用者无法有效的控制其声明周期,这样可能会违背一些最佳实践的编程原则。正是因为如此,在这个库里,我们不推荐使用单例。

由于下面几个原因,你在使用时需要从 Skiathos/Skopelos 进行继承:

  • 创建一个全局可共享的实例。
  • 重载 handleError(error: NSError) 方法,以便在你的程序里出现一些错误时,这个方法能够正常的被调用。

为了创建单例,你应该如下面的示例一样去从 Skiathos/Skopelos 进行继承:

单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@interface SkiathosClient : Skiathos
+ (SkiathosClient *)sharedInstance;
@end
static SkiathosClient *sharedInstance = nil;
@implementation SkiathosClient
+ (SkiathosClient *)sharedInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [self setupSqliteStackWithDataModelFileName:@"<#datamodelfilename>"];
</#datamodelfilename> });
return sharedInstance;
}
- (void)handleError:(NSError *)error
{
// clients should do the right thing here
NSLog(@"%@", error.description);
}
@end

或者是

1
2
3
4
5
6
7
class SkopelosClient: Skopelos {
static let sharedInstance = Skopelos(sqliteStack: "DataModel")
override func handleError(error: NSError) {
// clients should do the right thing here
print(error.description)
}
}

读写操作

写到这里,让我们同时看看在一个标准 Core Data 的操作方式和我们组件所提供的方式吧。

标准的读取姿势:

1
2
3
4
5
6
7
8
9
10
11
__block NSArray *results = nil;
NSManagedObjectContext *context = ...;
[context performBlockAndWait:^{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:NSStringFromClass(User)
inManagedObjectContext:context];
[request setEntity:entityDescription];
NSError *error;
results = [context executeFetchRequest:request error:&error];
}];
return results;

标准的写入姿势:

1
2
3
4
5
6
7
8
9
10
11
12
13
NSManagedObjectContext *context = ...;
[context performBlockAndWait:^{
User *user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass(User)
inManagedObjectContext:context];
user.firstname = @"John";
user.lastname = @"Doe";
NSError *error;
[context save:&error];
if (!error)
{
// continue to save back to the store
}
}];

Skiathos 中的读取姿势:

1
2
3
4
[[SkiathosClient sharedInstance] read:^(NSManagedObjectContext *context) {
NSArray *allUsers = [User allInContext:context];
NSLog(@"All users: %@", allUsers);
}];

Skiathos 中的写入姿势:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Sync
[[SkiathosClient sharedInstance] writeSync:^(NSManagedObjectContext *context) {
User *user = [User createInContext:context];
user.firstname = @"John";
user.lastname = @"Doe";
}];
[[SkiathosClient sharedInstance] writeSync:^(NSManagedObjectContext *context) {
User *user = [User createInContext:context];
user.firstname = @"John";
user.lastname = @"Doe";
} completion:^(NSError *error) {
// changes are saved to the persistent store
}];
// Async
[[SkiathosClient sharedInstance] writeAsync:^(NSManagedObjectContext *context) {
User *user = [User createInContext:context];
user.firstname = @"John";
user.lastname = @"Doe";
}];
[[SkiathosClient sharedInstance] writeAsync:^(NSManagedObjectContext *context) {
User *user = [User createInContext:context];
user.firstname = @"John";
user.lastname = @"Doe";
} completion:^(NSError *error) {
// changes are saved to the persistent store
}];

Skiathos 当然也支持链式调用:

1
2
3
4
5
6
7
8
9
10
11
__block User *user = nil;
[SkiathosClient sharedInstance].write(^(NSManagedObjectContext *context) {
user = [User createInContext:context];
user.firstname = @"John";
user.lastname = @"Doe";
}).write(^(NSManagedObjectContext *context) {
User *userInContext = [user inContext:context];
[userInContext deleteInContext:context];
}).read(^(NSManagedObjectContext *context) {
NSArray *users = [User allInContext:context];
});

如果是在 Swift中,代码将会变成下面这个样子

读取:

1
2
3
4
SkopelosClient.sharedInstance.read { context in
let users = User.SK_all(context)
print(users)
}

写入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Sync
SkopelosClient.sharedInstance.writeSync { context in
let user = User.SK_create(context)
user.firstname = "John"
user.lastname = "Doe"
}
SkopelosClient.sharedInstance.writeSync({ context in
let user = User.SK_create(context)
user.firstname = "John"
user.lastname = "Doe"
}, completion: { (error: NSError?) in
// changes are saved to the persistent store
})
// Async
SkopelosClient.sharedInstance.writeAsync { context in
let user = User.SK_create(context)
user.firstname = "John"
user.lastname = "Doe"
}
SkopelosClient.sharedInstance.writeAsync({ context in
let user = User.SK_create(context)
user.firstname = "John"
user.lastname = "Doe"
}, completion: { (error: NSError?) in
// changes are saved to the persistent store
})

链式调用:

1
2
3
4
5
6
7
8
9
10
11
12
SkopelosClient.sharedInstance.write { context in
user = User.SK_create(context)
user.firstname = "John"
user.lastname = "Doe"
}.write { context in
if let userInContext = user.SK_inContext(context) {
userInContext.SK_remove(context)
}
}.read { context in
let users = User.SK_all(context)
print(users)
}

NSManagedObject 类所提供了非常清楚的 CRUD 方法。在作为读/写代码块的参数传递之时,对象应该被作为一个整体进行处理。你应该优先使用这些内建的方法。主要的方法有下面这些:

1
2
3
4
5
6
7
+ (instancetype)SK_createInContext:(NSManagedObjectContext *)context;
+ (NSUInteger)SK_numberOfEntitiesInContext:(NSManagedObjectContext *)context;
- (void)SK_deleteInContext:(NSManagedObjectContext *)context;
+ (void)SK_deleteAllInContext:(NSManagedObjectContext *)context;
+ (NSArray *)SK_allInContext:(NSManagedObjectContext *)context;
+ (NSArray *)SK_allWithPredicate:(NSPredicate *)pred inContext:(NSManagedObjectContext *)context;
+ (instancetype)SK_firstInContext:(NSManagedObjectContext *)context;
1
2
3
4
5
6
7
static func SK_create(context: NSManagedObjectContext) -> Self
static func SK_numberOfEntities(context: NSManagedObjectContext) -> Int
func SK_remove(context: NSManagedObjectContext) -> Void
static func SK_removeAll(context: NSManagedObjectContext) -> Void
static func SK_all(context: NSManagedObjectContext) -> [Self]
static func SK_all(predicate: NSPredicate, context:NSManagedObjectContext) -> [Self]
static func SK_first(context: NSManagedObjectContext) -> Self?

注意,在使用 SK_inContext(context: NSManagerObjectContext) 时,不同的读写代码块可能会得到同一个对象。

线程安全

所有 DALService 所产生的实例都可以认为是线程安全的。

我们特别建议你在项目中进行这样的设置 -com.apple.CoreData.ConcurrencyDebug 1 ,这可以确保你不会在多线程和并发的情况下滥用 Core Data。

这个组件不是为了通过隐藏 ManagedObjectContext: 的概念来达到接口引入的目的:它将会在客户端中引入更多的线程问题,因为开发者有责任去检查所调用线程的类型(而那将会是在忽视 Core Data 所带给我们的好处)。





原文发布时间为:2016年09月10日

本文来自云栖社区合作伙伴掘金,了解相关信息可以关注掘金网站。

「最简单」的 Core Data 上手指南相关推荐

  1. Core Data 编程指南

    一.技术概览 1. Core Data 功能初窥 对于处理诸如对象生命周期管理.对象图管理等日常任务,Core Data框架提供了广泛且自动化的解决方案.它有以下特性. (注:对象图-Object g ...

  2. 「前端架构」Grab的前端学习指南

    原帖可以在Github上找到.未来的学习指南将在那里更新.如果你喜欢你正在阅读的东西,给它打一颗星吧! 公司是东南亚(SEA)领先的运输平台,我们的使命是利用公司最新的技术和人才,推动SEA前进.截至 ...

  3. 「红客联盟」网络安全简单入门与扫描

    「红客联盟」网络安全简单入门与扫描 1.安全策略 1.1.安全三要素 要全面地认识一个安全问题,我们有很多种办法,但首先要理解安全问题的组成属性.前人通过无数实践,最后将安全的属性总结为安全三要素,简 ...

  4. 苹果6屏幕多大_Apple Watch S5 现场上手:「不熄灭」的屏幕用起来怎么样?

    除了全新的 iPhone 11 系列手机以外,Apple Watch 也登上了昨晚的苹果发布会舞台.相信昨晚有追发布会的同学,对于这款产品都会有这样一种感觉,就是变化并不大. 确实单从外观来看,我们很 ...

  5. 无监督学习最新研究:通过简单的「图像旋转」预测便可为图像特征学习提供强大监督信号

    作者:Spyros Gidaris.Praveer Singh.Nikos Komodakis 「雷克世界」编译:嗯~是阿童木呀.KABUDA.EVA 在过去的几年中,深度卷积神经网络(ConvNet ...

  6. P6222 「P6156 简单题」(反演 + 积性函数线性筛)

    P6156 简单题 推式子 ∑i=1n∑j=1n(i+j)kf(gcd(i,j))gcd(i,j)=∑i=1n∑j=1n(i+j)kμ2(gcd(i,j))gcd(i,j)=∑d=1nμ2(d)dk+ ...

  7. ListView与.FindControl()方法的简单练习 #2 -- ItemUpdting事件中抓取「修改后」的值

    原文出處  http://www.dotblogs.com.tw/mis2000lab/archive/2013/06/24/listview_itemupdating_findcontrol_201 ...

  8. iOS:Core Data 中的简单ORM

    前2天在微博看到刘鑫等人对Android和iOS上数据库存储的ORM(对象关系映射,即把数据库映射到对象)的讨论.自己项目中需要存储的数据大多比较简单,所以无论是Android还是iOS都没有使用OR ...

  9. 我们采访了小鹏G3「高温抑菌」项目负责人,发现智能车OTA并不简单

    TechWeb原创 作者 |行者崟涛 G3作为小鹏汽车的开山之作,近两年在新能源市场上有着不俗的表现.2020年1至5月,G3全系的上险数量达到4394台,并在疫情期间销量保持着明显的增长趋势.数据显 ...

最新文章

  1. 题目1178:复数集合
  2. 怎么在网页中调用netmeeting使用阿?
  3. python trim函数_python strip()函数 介绍
  4. MongoRepository自定义条件及分页查询代码
  5. 0429 Scrum团队成立与第6-7章读后感
  6. 配置中心、消息队列、分布式服务链路跟踪
  7. Ubuntu 安装Wireshark
  8. Android学习笔记之Bitmap位图的缩放
  9. (13)数据结构-先序中序还原二叉树
  10. Android 7 soter,安卓首发!OPPO Find X全面支持微信人脸支付功能
  11. microsoft bing browser advance search
  12. 毕业论文(设计)开题报告
  13. 内功图说--十二段锦
  14. Win11图标变暗怎么办?Win11图标变暗的解决方法
  15. 微信小程序横屏字体变大的完美解决方案
  16. grep中使用\d匹配数字不成功的原因
  17. 储能系统双向DCDC变换器蓄电池充放电仿真模型有buck模式
  18. 系统动力学模型matlab仿真,MATLAB/Simulink动力学系统建模与仿真(带目录)_IT教程网...
  19. 广电BOSS系统简介
  20. Swift - 一个纯代码实现的登录界面(带猫头鹰动画效果)

热门文章

  1. Element UI格式化日期
  2. vue动态生成表单元素基础篇
  3. windows下可用mysql吗_Windows下MySQL安装配置与使用
  4. LVS+KEEPALIVED+nginx 7
  5. 2017第35周日乱记
  6. 2016年这些网络新贵或被并购
  7. Java 23种设计模式案例:原则及分类
  8. OSChina_IOS版客户端笔记(四)_程序数据、缓存的管理
  9. fastdfs上传文件时候报错
  10. 数据松弛Data Relaxation