Intermerdiate Fetching
获取你所需要的数据
通过
predicates
来提炼你所选择的结果在后台进行获取,不影响UI
直接更新一个对象在持久话存储区中,避免不必要的获取。
在真正开始前,你可以先看看之前写过的一片博客。在里面简单介绍了 CoreData 中获取数据。
CoreData的增删查改
当你有了大概的了解之后,我们来慢慢的体会。
我们的会在一个demo的基础上来进行。你可以在这里下载初始项目
也许你还需要了解NSPredicate1
NSpredicate2
NSFetchRequest
在之前的章节中,你已经学习到了通过创建一个存在的NSFetchRequest
来记录你从CoreData中获取的数据。我们这里可以通过四种不同的方法来创建这个对象。
1.
let fetchResult1 = NSFetchRequest()
let entity = NSEntityDescription.entityForName("Person", inManagedObjectContext: context)
fetchResult1.entity = entity
2.
let fetchResult2 = NSFetchRequest(entityName: "Person")
3.
let fetchResult3 = model.fetchRequestTemplateForName("peopleFR")
4.
let fetchResult4 = model.fetchRequestFromTemplateWithName("peopleFR", substitutionVariables: ["NAME" :"Ray"])
当然了,在不同的情况下使用合适的初始化方法。在接下来我们会使用到不同的初始化方法,来实例化一个NSFetchRequest
Stored fetch requests
我们来通过后两种方法来存储我们获取的数据
打开我们项目中的Bubble_Tea_Finder.xcdatamodeld
接着长按Add Entity
这个按钮,选择Add Fetch Request
,你将会创建一个新的 feetch request ,在左边的菜单中你可以编辑它。
我们改变它选取的的对象,选择Venue
,如果你需要给你的 fetch request添加额外的predicate
,你可以编辑fetch request 来添加条件
选择ViewController.swift
导入 Core Data
框架
import CoreData
添加两个属性
var fetchRequest: NSFetchRequest!
var venues: [Venue]!
在viewDidLoad
函数中添加下面的code
// 使用上边说到的第三种方式来初始化 `fetch request`
let model = coreDataStack.context.presistentStoreCoordinator!.managedObjectModel
fetchRequest = model.fetchRequestTemplateForName("FetchRequest")
fetchAndReload()
在上边的代码中,我们通过model
来初始化了 fetchRequest
。这种初始化方法是当我们有一个fetch request
模版的时候使用。
Note: NSManagedObjectModel’s fetchRequestTemplateForName() takes a string identifier. This identifier must exactly match whatever name you chose for your fetch request in the model editor. Otherwise, your app will throw an exception and crash. Whoops!
上边的代码中我们在最后一行中调用了fetchAndReload
这个方法
获取数据,更新界面
func fetchAndReload() {
do {
//在上下文 执行我们的查询 ,将查询的结果 转成 [Venue]类型
venues = try coreDataStack.context.executeFetchRequest(fetchRequest) as! [Venue]
tableView.reloadData()
} catch let error as NSError { print("Could not fetch \(error), \(error.userInfo)")
}
}
同时呢,我们还需要更改一些别的代码:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return venues.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPatch indexPatch: NSIndexPatch) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(venueCellIdentifier)!
let venur = venues[indexPatch.row]
cell.textLabel!.text = venue.name
cell.detailTextLabel!.text = venue.priceInfo?.priceCategory
return cell
}
现在呢,你可以运行一个app,来看看界面上的数据发生了什么改变。
Fetching different result types
别小看了 NSFetchRequest
这个类,它也有很多方法 (>You can use it to fetch individual values, compute statistics on your data such as the average, minimum and maximum, and more.)
NSFetchRequest
有一个属性 resultType
他是一个NSManagedObjectResultType
类型
NSManagedObjectResultType: Returns managed objects (default value).
NSCountResultType: Returns the count of the objects that match the fetch
request.NSDictionaryResultType: This is a catch-all return type for returning the results of different calculations.
NSManagedObjectIDResultType: Returns unique identifiers instead of full- fledged managed objects.
我们来看看这些概念在实际中是怎么应用的吧。
Returning a count
打开FilterViewController.swift
还是像之前那样,我们首先需要倒入Core Data
import CoreData
添加下边的这个属性: var coreDataStack: CoreDataStack
我们要让它持有我们的Core Data Stack
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == filterViewControllerSeguIdentifier {
let navController = segue.destinationViewController as! UINavigationController
let filterVC = navController.topViewController as! FilterViewController
filterVC.coreDataStack = coreDataStack
}
}
Go back to FilterViewController.swift and add the following lazy property:
//这个属性定义了我们 要查询的规则。
lazy var cheapVenuePredicate: NSPredicate = {
var predicate = NSPredicate(format: "priceInfo.priceCategory == %@","$")
return predicate
}()
上边的代码就是说,选择出 priceInfo.priceCategory
的值为$
接下来,我们来完成下边的方法
func populateCheapVenueContLabel() {
//通过 `Venue`来实例化一个`NSFetchRequest`
let fetchRequest = NSFetchRequest(entityName: "Venue")
//resultType 为 CountResultType
fetchRequest.resultType = .CountResultType
// predicate 为之前定义的 cheapVenuePredicate
fetchRequest.predicate = cheapVenuePredicate
do{
//通过 上下文来执行查找
let results = try coreDataStack.context.executeFetchRequest(fetchRequest) as! [NSNumber]
let count = results.first!.integerValue
firstPricaeCategoryLabel.text = "\(count)bubble tea places"
} catch let error as NSError {
print("Could not fetch \(error),\(error.userInfo)")
}
}
在ViewDidLoad
方法中调用上边定义的函数
override func viewDidLoad() {
super.viewDidLoad()
populateCheapVenueContLabel()
}
现在你运行你的程序,点击Filter按钮就可以看到你筛选出来的数据
你已经了解了 count result type ,现在可以很快速的获取第二类价格的count了
//定义我们筛选的规则
lazy var moderateVenuePredicate: NSPredicate = {
var predicate = NSPredicate(format: "priceInfo.priceCategory == %@", "$$")
return predicate
}()
func populateModerateVenueCountLabel() {
// $$ fetch request
//初始化 fetch request
let fetchRequest = NSFetchRequest(entityName: "Venue")
// resulttype 为 countresulttype
fetchRequest.resultType = .CountResultType
fetchRequest.predicate = moderateVenuePredicate
do {
let results = try coreDataStack.context.executeFetchRequest(fetchRequest) as [NSNumber]
let count = results.first!.integerValue
secondPriceCategoryLabel.text = "\(count)buble tea places"
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
}
最后在ViewDidLoad()
中执行你定义的方法
override func viewDidLoad() {
super.viewDidLoad()
populateCheapVenueCountLabel()
//add the line below
populateModerateVenueCountLabel()
}
An alternate way to fetch a count
我们来通过另外一种方式来获取 count
lazy var expensiveVenuePredicate: NSPredicate = {
var predicate = NSPredicate(formate:"priceInfo.priceCategory == %@" ," $$$")
return predicate
}()
func populateExpensiveVenueCountLabel() {
// $$$ fetch request
let fetchRequest = NSFetchRequest(entityName: "Venue") fetchRequest.predicate = expensiveVenuePredicate
var error: NSError?
let count = coreDataStack.context.countForFetchRequest(fetchRequest,
error: &error)
if count != NSNotFound {
thirdPriceCategoryLabel.text = "\(count) bubble tea places"
} else {
print("Could not fetch \(error), \(error?.userInfo)")
}
}
你会发先我们这次查询的时候执行了不同的方法。但是,还是像我们之前一样,我们实例化了一个fetch request 对象 根据 Venue 对象,设置它的 predicate
是我们之前定义的 expensiveVenuePredicate
不同的地方在于最后的几行代码,我们这里没有设置NSCountResultType
,相反,我们使用了countForFetchRequest
方法,来替代了executeFetchRequest
,直接获取到了 count
记得在 viewDidLoad
中调用你写的方法,然后尽情的运行你的app 来看看数据是否发生了改变。
Performing calculations with fetch requests
接下来呢我们来让获取到的结果进行一些运算。CoreData支持了一些不同的函数,例如:avaerage,sum,min,max ...
还是在 FilterViewController.swift
文件中,添加下边这个方法。之后我们会详细说说,这段代码都做了些什么。
func populateDealsCountLabel() {
//1
let fetchRequest = NSFetchRequest(entityName: "Venue")
fetchRequest.resultType = .DictionaryResultType
//2
let sumExpressionDesc = NSExpressionDescription()
sumExpressionDesc.name = "sumDeals"
//3
sumExpressionDesc.expression = NSExpression(forFunction: "sum:", arguments: [NSExpression(forKeyPath: "specialCount")])
sumExpressionDesc.expressionResultType = .Integer32AttributeType
//4
fetchRequest.propertiesToFetch = [sumExpressionDesc]
//5
do {
let results = try coreDataStack.context.executeFetchRequest(fetchRequest) as! [NSDictionary]
let resultDict = results.first!
let numDeals = resultDict["sumDeals"]
numDealsLabel.text = "\(numDeals!) total deals"
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
}
首先创建检索地点对象的读取请求,接下来指定结果类型是
DictionaryResultType
创建一个
NSExpressionDescription
来请求和,并且给他取名为sumDeals,这个就可以从字典中读取出他的结果。给
NSExpressionDescription
一个具体的NSExpression
,你想要的和函数。最后你需要说明返回的数据类型。所以将其设置为Integer32AttributeType
告诉你声明的fetch request 设置
propertiesToFetch
属性为你创建的NSExpression
最终执行这个 fetch requset
What other functions does Core Data support? To name a few: count, min, max, average, median, mode, absolute value and many more. For a comprehensive list, check out Apple’s documentation for NSExpression.
在viewDidload中执行你定义的函数。
到现在你已经使用了三种NSFetchRequset
支持的 result types:
.ManagedObjectResultType
, .CountResultType
and .DictionaryResultType
还有一个 .ManagedObjectIDResltType
我们还没有使用过,当你使用这个 类型的时候,返回的结果是一个NSManagedObjectID
数组 。一个NSManagedObjectID
一个 managed object 的 id,它就像一个唯一的健值在数据库中。
在 iOS 5 时,通过 ID 查询是十分流行的,因为NSManagedObjectID
是线程安全的。
Now that thread confinement has been deprecated in favor of more modern concurrency models, there’s little reason to fetch by object ID anymore.
在 FilterViewController.swift
文件中添加一个协议
protocol FilterViewControllerDelegate: class {
func filterViewController(filter: FilterViewController,
didSelectPredicate predicate:NSPredicate?,
sortDescriptor:NSSortDescriptor?)
}
This protocol defines a delegate method that will notify the delegate that the user selected a new sort/filter combination.
接下来定义下边的属性
weak var delegate: FilterViewControllerDelegate?
var selectedSordescriptor: NSSortDescriptor?
var selectedPredicate: NSPredicate?
接下来我们要在点击了搜索之后,将我们筛选的条件传回第一个界面。
@IBAction func saveButtonTapped(sender: UIBarButtonItem) {
delegate?.filterViewController(self, didSelectPredicate: selectedPredicate, sortDescriptor: selectedSordescriptor)
dismissViewControllerAnimated(true, completion:nil)
}
接下来我们就要确定我们是选择的哪一个筛选条件呢。
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)!
switch cell {
case cheapVenueCell:
selectedPredicate = cheapVenuePredicate
case moderateVenueCell:
selectedPredicate = moderateVenuePredicate
case expensiveVenueCell:
selectedPredicate = expensiveVenuePredicate
default:
debugPrint("default case")
}
cell.accessoryType = .Checkmark
}
通过匹配点击的是哪一个cell来给我们的selectedPredicate
赋值。
让我们回到selectedPredicate
来实现这个协议。
extension ViewController: FilterViewControllerDelegate {
func filterViewController(filter: FilterViewController,
didSelectPredicate predicate:NSPredicate?,
sortDescriptor:NSSortDescriptor?) {
fetchRequest.predicate = nil
fetchRequest.sortDescriptors = nil
if let fetchPredicate = predicate {
fetchRequest.predicate = fetchPredicate
}
if let sr = sortDescriptor {
fetchRequest.sortDescriptors = [sr]
}
fetchAndReload()
tableView.reloadData()
}
}
当我们的界面返回时,就会把我们的筛选条件传过来,我们来赋值给fetchRequest.predicate
和 fetchRequest.sortDescriptors
然后重新获取数据,刷新界面。
在运行前我们还需要做一件事情
//add line below filterVC.coreDataStack = coreDataStack
filterVC.delegate = self
修改下边代码
override func viewDidLoad() {
super.viewDidLoad()
fetchRequest = NSFetchRequest(entityName: "Venue")
fetchAndReload()
}
现在来运行app 通过选择不同的$
来看我们的显示结果。
在 FilterViewController.swift
中添加下边的 lazy属性, 都是NSPredicate
lazy var offeringDealPredicate: NSPredicate = { var pr = NSPredicate(format: "specialCount > 0")
return pr
}()
lazy var walkingDistancePredicate: NSPredicate = { var pr = NSPredicate(format: "location.distance < 500")
return pr
}()
lazy var hasUserTipsPredicate: NSPredicate = { var pr = NSPredicate(format: "stats.tipCount > 0")
return pr
}()
接下来我们像上边一样,如法炮制
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)!
switch cell {
case cheapVenueCell:
selectedPredicate = cheapVenuePredicate
case moderateVenueCell:
selectedPredicate = moderateVenuePredicate
case expensiveVenueCell:
selectedPredicate = expensiveVenuePredicate
case offeringDealCell:
selectedPredicate = offeringDealPredicate
case userTipsCell:
selectedPredicate = hasUserTipsPredicate
case walkingDistanceCell:
selectedPredicate = walkingDistancePredicate
default:
debugPrint("default case")
}
cell.accessoryType = .Checkmark
}
Sorting fetched results
NSFetchRequest
的另一个强大的功能是它能够为您挑选获取结果的能力。它通过使用另一种方便的基础类,NSSortDescriptor
做到这一点。这些种类发生在SQLite的水平,而不是在存储器中。这使得核心数据排序快捷,高效。
我们来创建三个 NSSortDescriptor
lazy var nameSortDescriptor: NSSortDescriptor = {
var sd = NSSortDescriptor(key: "location.distance", ascending: true)
return sd
}()
lazy var distanceSortDescriptor: NSSortDescriptor = {
var sd = NSSortDescriptor(key: "name", ascending: true, selector: "localizedStandardCompare:")
return sd
}()
lazy var priceSortDescriptor: NSSortDescriptor = {
var sd = NSSortDescriptor(key: "priceInfo.priceCategory",
ascending: true)
return sd
}()
如法炮制
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)!
switch cell {
...
case nameAZSortCell:
selectedSordescriptor = nameSortDescriptor
case nameZASortCell:
selectedSordescriptor = nameSortDescriptor.reversedSortDescriptor as? NSSortDescriptor
case priceSortCell:
selectedSordescriptor = priceSortDescriptor
default:
debugPrint("default case")
}
cell.accessoryType = .Checkmark
}
你会发现有一点点的不同,就在
selectedSordescriptor = nameSortDescriptor.reversedSortDescriptor as? NSSortDescriptor
运行你的app 来选择 SORY BY 中的选项来看看效果。
Asynchronous fetching
如果你已经远远得到这一点,有两个好消息和坏消息(然后是更多的好消息)。你已经学到了很多关于你可以用一个简单的NSFetchRequest做什么好消息了。坏消息是,每次取到目前为止您已经执行请求阻塞主线程,而你等待你的结果回来。
当您阻止主线程,它使屏幕反应迟钝传入触摸,并创建其他问题摆。你有没有觉得这种阻塞主线程,因为你做了简单读取,只有一次取了几个对象的请求。
由于核心数据的开始,这个框架已经给开发者多种技术来在后台执行读取操作。在iOS中8,核心数据现在有结束的时候取在后台执行长时间运行提取请求并获得一个完成回调的API。
我们来看看这个新的 API 作用,返回我们的ViewController.swift
来添加下边的属性:
var asyncFetchRequest: NSAsynchronousFetchRequest!
不要被 NSAsynchronousFetchRequest
他的名字所迷惑,他不依赖于NSFetchRequest
相反他继承自NSPersistentStoreRequest
我们在viewDidLoad
中来实例化这个对象。
override func viewDidLoad() {
super.viewDidLoad()
//1
fetchRequest = NSFetchRequest(entityName:"Venue")
//2
asyncFetchRequest = NSAsynchronousFetchRequest(fetchRequest:fetchRequest){
[unowned self] (result:NSAsynchronousFetchResult!) -> Void in
self.venues = result.finalResult as! [Venue]
self.tableView.reloadData()
}
}
//3
do{
try coreDataStack.context.executeRequest(asyncFetchRequest)
}catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
来看看上边的代码都做了些什么
通过
entityName
来实例化一个NSFetchRequest
通过刚刚
NSFetchRequest
对象来实例化一个NSAsynchronousFetchRequest
,对了还有一个回调闭包 。你要获取的 venues 被包含在 NSAsynchronousFetchRequest 的 finalResult 属性中。你还需要去执行这个异步的获取。
在执行完了
executeRequest()
之后,你不要做任何的动作。
Note: As an added bonus to this API, you can cancel the fetch request with NSAsynchronousFetchResult’s cancel() method.
如果你此时运行程序的话,会 carsh掉。这里呢还需要一点改变
var venues: [Venue]! = []
这是因为,你的数据是异步获取的。当你的数据还没有获取到的时候,视图已经开始加载了,但是呢,你还没有给 venues 初始化,所以呢,这里我们将给它一个空的数组。以至于我们的视图可以默认加载没有数据的视图。
Batch updates: no fetching required
有时候,你需要从coreData中获取一些对象来改变他们的属性值。你改变之后呢,还需要去把数据再保存到持久话存储区。这是很自然的方式
但是,如果你又很多数据需要修改哪?如果你还是那么做的话将会浪费掉很多的时间和内存。
幸运的是,在 iOS8 Apple 介绍了 批处理更新,一个新的方式去更新你的Core Data 对像。他不用将你的对象获取到内存中来改变值然后再存储。总之就是提高了效率,提高了效率,减少了时间,减少了时间。
我么来练习一下:
在 viewDidLoad()
的 super.viewDidLoad()
的下边添加下面的代码:
let batchUpdate = NSBatchUpdateRequest(entityName:Venue)
batchUpdate.propertiesToUpdate = ["favorite":NSNumber(bool:true)]
batchUpdate.affectedStores = coreDataStack.context.presistentStoreCoordinator!.persistentStores
batchUpdate.resultType = .UpdateObjectsCountResultType
do {
let batchResult = try coreDataStack.context.executeRequest(batchUpdate) as! NSBatchUpdateResult
print("Records updated \(batchResult.result!)")
} catch let error as NSError {
print("Could not update \(error), \(error.userInfo)")
}
当然也有批处理删除了。在iOS9 Apple 介绍了 NSBatchDeleteRequest
,你可以去尝试一下。
Note: Since you’re sidestepping your NSManagedObjectContext, you won’t get any validation if you use a batch update request or a batch delete request. Your changes also won’t be reflected in your managed context. Make sure you’re sanitizing and validating your data properly before using this new feature!