iOS 2018系列:破解iOS采访或成为iOS专家(7)

第7章:核心数据与并发

假设您正在开发一个小型或简单的应用程序,那么您可能看不到在后台运行Core Data操作的好处。 但是,如果在第一次启动应用程序时在主线程上导入了数百或数千条记录,将会发生什么情况? 结果可能是戏剧性的。 当使用核心数据时,我们在现实的编程世界中都面临着所有这些问题,因此让我们讨论解决方案。

基本:

核心数据是用于管理应用程序中的模型层对象的框架

并发是同时处理多个队列上的数据的能力。

脚步:

*初始化核心数据栈

  1. 使用模型文件创建管理对象模型实例
  2. 使用管理对象模型实例创建持久性存储协调器
  3. 使用SQLite或SQLite dB的其他类型和文件路径在PSC实例中添加永久存储
  4. 创建并发类型为NSMainQueueConcurrencyTypeNSPrivateQueueConcurrencyType托管对象上下文。
  5. 在“管理”对象模型实例中设置持久性存储协调器。

*创建和保存托管对象

  1. 使用NSEntityDescription实体创建实体
  2. 使用实体和MOC创建需要插入的行实例,该实例将成为被管理对象实例。
  3. 在MO实例中设置Value
  4. 将MO条目保存在核心数据中-上下文。 保存()

*提取对象

  1. 准备实体的NSFetchRequest类型的请求

let request = NSFetchRequest (实体名称:“用户”)

2.从上下文中以[NSManagedObject]数组的形式获取结果

让结果=尝试context.fetch(request)

3.遍历数组以获取特定键的值

对于数据结果! [NSManagedObject]

核心数据堆栈设置-简单方法(NSPersistentContainer)

设置核心数据堆栈需要一些工作。 典型的设置需要许多步骤:

  • 从应用程序捆绑包中加载托管对象模型
  • 使用模型创建一个持久性存储协调器
  • 要求协调器加载持久性存储
  • 创建一个或多个托管对象上下文

从iOS 10开始, NSPersistentContainer 删除了大部分样板代码。 在应用程序中封装核心数据堆栈的容器。

如果采用一些简单的命名约定和默认存储位置,则核心数据栈的设置可以像这样简单:

 container = NSPersistentContainer(name: "storeName") 
container.loadPersistentStores { (storeDescription, error) in
if let error = error {
fatalError("Failed to load store: \(error)")
}
}

加载商店之前,您需要设置商店描述。

持久性存储描述符

持久性容器将持久性存储的设置收集到持久性存储描述符中 。 要覆盖默认值,请在加载商店之前创建一个新的描述符。 使用商店的URL创建一个持久性商店描述符。最常用的配置设置是:

  • typeString常量,指定商店类型(默认为NSSQLLiteStoreType )。
  • isReadOnly :将只读存储区的Bool设置为true (默认为false )。
  • shouldAddStoreAsynchronouslyBool设置为true ,以在后台线程上异步将商店添加到协调器。 默认值为false ,它将在调用线程上同步添加存储。
  • shouldInferMappingAutomatically :如果标志shouldMigrateStoreAutomaticallytrue Bool ,尝试在迁移时推断映射模型。 默认值为true
  • shouldMigrateStoreAutomaticallyBool自动迁移商店。 默认值为true

因此,要创建一个在后台线程上异步加载的只读持久性存储(默认情况下会自动迁移):

 let url = NSPersistentContainer.defaultDirectoryURL() 
let description = NSPersistentStoreDescription(url: url)
description.shouldAddStoreAsynchronously = true
description.isReadOnly = true
container.persistentStoreDescriptions = [description]

获取主视图上下文

持久性容器具有一个方便的只读属性,名为viewContext以获取主队列托管对象上下文 。 顾名思义,这是使用用户界面时应使用的上下文。

 container.viewContext.perform({ 
// ...
})

执行后台任务

为了避免阻塞用户界面,您不应将主视图上下文用于耗时的任务。 创建一个私有管理对象上下文,并在后台执行任务。 持久性容器有一个方便的方法,可以为您创建一个临时的私有上下文,并执行一个代码块:

 container.performBackgroundTask({ (context) in 
// ... do some task on the context
  // save the context 
do {
try context.save()
} catch {
// handle error
}
})

获取私有上下文

您还可以通过自己认为合适的方式获取新的私有上下文:

 let context = persistentContainer.newBackgroundContext() 
context.perform({
// ...
})

核心数据,多线程和主线程

从理论上讲,在多个线程上使用Core Data实际上非常简单。 NSManagedObjectNSManagedObjectContextNSPersistentStoreCoordinator不是线程安全的。

线程限制:为与Core Data交互的每个线程创建一个托管对象上下文。 NSManagedObjectContext类不是线程安全的。

到目前为止,我们已经了解到,如果您在多个线程上执行Core Data操作,则需要多个托管对象上下文。 但是,需要注意的是,管理对象上下文不知道彼此的存在。 在一个管理对象上下文中对管理对象所做的更改不会自动传播到其他管理对象上下文。 我们如何解决这个问题?

Apple建议使用两种流行的策略:通知和父子托管对象上下文。

  1. 通知事项

托管对象上下文发布三种类型的通知:

  • NSManagedObjectContextObjectsDidChangeNotification :当托管对象上下文的一个托管对象已更改时,将发布此通知。
  • NSManagedObjectContextWillSaveNotification :在托管对象上下文执行保存操作之前,将发布此通知。
  • NSManagedObjectContextDidSaveNotification :在托管对象上下文执行保存操作后,将发布此通知。

当托管对象上下文通过持久性存储协调器将其更改保存到持久性存储时,其他托管对象上下文可能希望了解这些更改。

2. 父/子管理对象上下文

创建一个子托管对象上下文与到目前为止所看到的仅稍有不同。 我们通过调用init(concurrencyType:)初始化一个子托管对象上下文。 初始化程序接受的并发类型定义了托管对象上下文的线程模型。 让我们看一下每种并发类型。

  • MainQueueConcurrencyType :只能从主线程访问托管对象上下文。 如果尝试从任何其他线程访问它,则会引发异常。
  • PrivateQueueConcurrencyType :当创建并发类型为PrivateQueueConcurrencyType的托管对象上下文时,该托管对象上下文与一个专用队列相关联,并且只能从该专用队列中访问它。
  • ConfinementConcurrencyType :这是与我们之前探讨的线程限制概念相对应的并发类型。 如果使用init()创建托管对象上下文,则该托管对象上下文的并发类型为ConfinementConcurrencyTypeApple从iOS 9开始不赞成使用这种并发类型。这也意味着 从iOS 9开始不建议使用 init()

当Apple引入父/子托管对象上下文时,Core Data框架中添加了两个关键方法, performBlock(_:)performBlockAndWait(_:) 。 两种方法都会使您的生活更加轻松。 当您在托管对象上下文上调用performBlock(_:)并传入要执行的代码块时,Core Data确保该块在正确的线程上执行。 对于PrivateQueueConcurrencyType并发类型,这意味着将在该受管对象上下文的专用队列上执行该块。

有关更多详细信息— https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/Concurrency.html#//apple_ref/doc/uid/TP40001075-CH24-SW1

奖励时间:@IBDesignable和@IBInspectable

IB可设计

IBDesignable属性将标识UIView或从UIView继承的元素-例如:UIButton,UIImageView,UILabel等。

例如,我创建了一个Custom UIButton类,

  @IBDesignable 
打开类MYHighLightedButton:UIButton {
 公共替代init(框架:CGRect){ 
super.init(frame:框架)
setTitle(“ MyTitle”,用于:.normal)
setTitleColor(UIColor.blue,for:.normal)
}
 公用必需的init?(编码器aDecoder:NSCoder){ 
super.init(编码器:aDecoder)
}
  } 

您可以在xib中添加自定义视图,在Interface Builder(`StoryBoard`)中拖放UIButton。 转到“ 显示身份检查器 ”,并将“类和模块”设置为MYHighLightedButton

IB检查

让我们在按钮上添加一些自定义属性。 🙌

为此,我们必须使用IBInspectable属性。 让我们看看如何添加它们。

  @IBInspectable 
public var cornerRadius:CGFloat = 2.0 {
didSet {
self.layer.cornerRadius = self.cornerRadius
}
}

这将把corner_radius属性添加到您的按钮。 您将能够在Attributes Inspector中看到它们

如何仅读取实体的一些属性?

我们可以使用NSFetchRequest类的属性“ setPropertiesToFetch”。 我们可以在setPropertiesToFetch方法中以字符串格式传递属性数组。

核心数据与Sqlite?

核心数据适用于对象图管理。 对存储在内存中的对象进行操作。 可以很快在内存中创建数百万个新对象。

Sqlite适用于带有SQL查询的表结构。 在磁盘上存储的数据上操作。 创建磁盘数以百万计的新行可能很慢,因为它是磁盘I / O操作,并且具有诸如主键,复合键之类的键。

知识就是力量!

希望您能从中学到东西。
下一章 :泛型,LRU快速缓存