前面三节中,我们使用了最简单的方法来保存或获取CoreData中的数据比如:获取所有的领结实例,但是有些时候,你可能想给予fetch更多的掌控,通过这节的学习,你将学会以下知识:
- 只获取你需要的数据;
- 使用predicates来优化fetch的结果;
- 在后台进程中进行fetch操作而不阻塞UI进程;
- 直接更新persistent store而减少不必要的fetch操作;
转载请注明出处:http://blog.csdn.net/yamingwu/article/details/42318091
源代码地址:https://github.com/dnawym/StudySwift/tree/master/CoreData/Bubble%20Tea%20Finder
NSFetchRequest:
前面章节中我们已经学到,NSFetchRequest实例用于从Core Data中获取数据,配置这个类的实例并将其交给NSManagedObjectContext,后后者会帮我们完成后续的工作。构造fetch request的方法有如下四种:
第一种:创建NSFetchRequest的实例和NSEntityDescription的实例,将entity赋值给fetch request
let fetchRequest1 = NSFetchRequest()
let entity = NSEntityDescription.entityForName("Person", inManagedObjectContext: managedObjectContext!)
fetchRequest1.entity = entity!
第二种:将entity的名称作为参数传递给fetch request,这样就不用显示的构造entity
let fetchRequest2 = NSFetchRequest(entityName: "Person")
第三种:使用NSManagedObjectModel来构造fetch request,这一节我们会使用这种方法
let fetchRequest3 = managedObjectModel.fetchRequestTemplateForName("peoperFR")
第四种:和第三种方法类似,但是会传递额外的参数给NSManagedObjectModel,这些额外的参数用于优化fetch结果
let fetchRequest4 = managedObjectModel.fetchRequestFromTemplateWithName("peopleFR", substitutionVariables: ["NAME" : "Ray"])
珍珠奶茶:
这款应用程序使用从Foursqure得到纽约市内30个珍珠奶茶点作为数据源(json),给据给定的过滤条件在table view中进行显示。
创建fetch request:
打开Model。xcdatamodeld左键按住Add Entity不放,选择弹出对话框中的第二个选项“Add Fetch Request”
这样就创建了我们需要的一个Fetch Request,我们暂时不需要为新创建的fetch request添加其它条件
接下来,我们在ViewController里面使用这个FetchRequest,添加变量的定义
import CoreData
var fetchRequest: NSFetchRequest!
var venues: [Venue]!
修改viewDidLoad,通过managed object model来获取我们创建的FetchRequest。fetchRequestTemplateForName使用字符串来查找我们使用model editor创建的FetchRequest
override func viewDidLoad() {
super.viewDidLoad()
fetchRequest = coreDataStack.model.fetchRequestTemplateForName("FetchRequest")
fetchAndReload()
}
fetchAndReload函数顾名思义,从core data获取数据并刷新table view
//MARK: - 辅助函数
func fetchAndReload() {
var error: NSError?
let results = coreDataStack.context.executeFetchRequest(fetchRequest, error: &error) as [Venue]?
if let fetchResults = results {
venues = fetchResults
} else {
println("Could not fetch \(error), \(error!.userInfo)")
}
tableView.reloadData()
}
修改tableView的2个函数numberOfRowsInSection和cellForRowAtIndexPath
func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return venues.count
}
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell = tableView.dequeueReusableCellWithIdentifier("VenueCell") as UITableViewCell
let venue = venues[indexPath.row]
cell.textLabel!.text = venue.name
cell.detailTextLabel!.text = venue.priceInfo.priceCategory
return cell
}
程序执行效果如下:
获取不同类型的结果:
至此,你可能认为NSFetchRequest这个类是一个很简单的工具,设置一些属性,你就可以得到所需的数据。其实,NSFetchRequest像瑞士军刀一样,有这多种多样的功能。比如:获取一个单独的数据,计算统计结果(平均值、最小值和最大值等等)。
- NSFetchRequest有一个名为resultType的属性,我们现在只是用了.Default值,除了这个值还有如下几种:
- NSManagedObjectResultType:返回一个managed object(默认值)
- NSCountResultType:返回满足fetch request的object数量
- NSDictionaryResultType:
- NSManagedObjectIDResultType:返回唯一的标示符而不是managed object
计算不同价位商家的数目:
修改FilterViewController.swift,添加最低价位的predicate函数:
// 这个NSPredicate用于计算属于最低价格区间的venues数量
lazy var cheapVenuePredicate: NSPredicate = {
var predicate = NSPredicate(format: "priceInfo.priceCategory == %@", "$")
return predicate!
}()
添加函数查询最低价位商家的数目并更新到Label上:
func populateCheapVenueCountLabel() {
let fetchRequest = NSFetchRequest(entityName: "Venue")
fetchRequest.resultType = .CountResultType
fetchRequest.predicate = cheapVenuePredicate
var error: NSError?
let result = coreDataStack.context.executeFetchRequest(fetchRequest, error: &error) as [NSNumber]?
if let countArray = result {
let count = countArray[0].integerValue
firstPriceCategoryLabel.text = "\(count) bubble tea pleases"
} else {
println("Could not fetch \(error), \(error!.userInfo)")
}
}
重载viewDidLoad函数
override func viewDidLoad() {
super.viewDidLoad()
populateCheapVenueCountLabel()
}
接下来添加中等价位和高价位商家的数量
lazy var moderateVenuePredicate: NSPredicate = {
var predicate = NSPredicate(format: "priceInfo.priceCategory == %@", "$$")
return predicate!
}()
func populateModerateVenueCountLabel() {
let fetchRequest = NSFetchRequest(entityName: "Venue")
fetchRequest.resultType = .CountResultType
fetchRequest.predicate = moderateVenuePredicate
var error: NSError?
let result = coreDataStack.context.executeFetchRequest(fetchRequest, error: &error) as [NSNumber]?
if let countArray = result {
let count = countArray[0].integerValue
secondPriceCategoryLabel.text = "\(count) bubble tea pleases"
} else {
println("Could not fetch \(error), \(error!.userInfo)")
}
}
lazy var expensiveVenuePredicate: NSPredicate = {
var predicate = NSPredicate(format: "priceInfo.priceCategory == %@", "$$$")
return predicate!
}()
func populateExpensiveVenueCountLabel() {
let fetchRequest = NSFetchRequest(entityName: "Venue")
fetchRequest.resultType = .CountResultType
fetchRequest.predicate = expensiveVenuePredicate
var error: NSError?
let result = coreDataStack.context.executeFetchRequest(fetchRequest, error: &error) as [NSNumber]?
if let countArray = result {
let count = countArray[0].integerValue
thirdPriceCategoryLabel.text = "\(count) bubble tea pleases"
} else {
println("Could not fetch \(error), \(error!.userInfo)")
}
}
更新viewDidLoad函数
override func viewDidLoad() {
super.viewDidLoad()
populateCheapVenueCountLabel()
populateModerateVenueCountLabel()
populateExpensiveVenueCountLabel()
}
程序filter页面效果如下,低价位商家27家,中等价位2家,高价位1家:
对fetch request进行计算:
首先计算deal的和,设置返回值类型是.DictionaryResultType
func populateDealsCountLabel() {
// 创建FetchRequest获取Venue对象并设置返回值类型为DictionaryResultType
let fetchRequest = NSFetchRequest(entityName: "Venue")
fetchRequest.resultType = .DictionaryResultType
// 创建NSExpressionDescription来请求进行总和计算,取名为sumDeals,一会儿就可以通过这个名字
// 从fetch请求返回的字典中找到总和
let sumExpressionDesc = NSExpressionDescription()
sumExpressionDesc.name = "sumDeals"
// 指定要进行总和计算的字段名-specialCount并设置返回值类型
sumExpressionDesc.expression = NSExpression(forFunction: "sum:", arguments:[NSExpression(forKeyPath: "specialCount")])
sumExpressionDesc.expressionResultType = .Integer32AttributeType
// 设置fetchRequest的propertiesToFetch属性为我们构造的sumExpressionDesc告诉fetchRequest
// 我们需要对数据进行求和
fetchRequest.propertiesToFetch = [sumExpressionDesc]
// 执行fetch request,将返回结果转换为optional字典,使用设置的字段名-sumDeals来索引求和的结果
var error: NSError?
let result = coreDataStack.context.executeFetchRequest(fetchRequest, error: &error) as [NSDictionary]?
if let resultArray = result {
let resultDict = resultArray[0]
let numDeals: AnyObject? = resultDict["sumDeals"]
numDealsLabel.text = "\(numDeals!) total deals"
} else {
println("Could not fetch \(error), \(error!.userInfo)")
}
}
最后一种返回值类型是.ManagedObjectIDResultType,这种类型的fetch结果返回值是NSManagedObjectID的数组,类似于数据库的主键。
由于每一次fetch,可能返回大量的数据,这样每一个fetch都会消耗大量的内存,接下来我们使用Predicate来限制返回的数据的数量。
为FilterViewController类添加protocol,这样实现了这个protocol的类在用户修改了排序或过滤条件后就会被通知到。
protocol FilterViewControllerDelegate: class {
func filterViewController(filter: FilterViewController,
didSelectPredicate predicate: NSPredicate?,
sortDescriptor: NSSortDescriptor?)
}
添加delegate,选定的NSSortDescriptor和NSPredicate的定义
weak var delegate: FilterViewControllerDelegate?
var selectedSortDescriptor: NSSortDescriptor?
var selectedPredicate: NSPredicate?
修改saveButtonTapped函数和didSelectRowAtIndexPath。当用户点击价格分类cell时,更新selectedPredicate。当用户点击search按钮时,调用代理的filterViewController的函数。
//MARK - UITableViewDelegate methods
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:
println("default case")
}
cell.accessoryType = .Checkmark
}
// MARK - UIButton target action
@IBAction func saveButtonTapped(sender: UIBarButtonItem) {
delegate!.filterViewController(self, didSelectPredicate: selectedPredicate, sortDescriptor: selectedSortDescriptor)
dismissViewControllerAnimated(true, completion:nil)
}
回到ViewController,实现代理函数
//MARK: - FilterViewControllerDelegate methods
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()
}
程序执行效果如下,在过滤页面选择只显示中等价位的商家:
类似的方法,实现其它三种predicate
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!
}()
更新didSelectRowAtIndexPath函数
//MARK - UITableViewDelegate methods
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 walkingDistanceCell:
selectedPredicate = walkingDistancePredicate
case userTipsCell:
selectedPredicate = hasUserTipsPredicate
default:
println("default case")
}
cell.accessoryType = .Checkmark
}