Intermerdiate Fetching

  1. 获取你所需要的数据

  2. 通过predicates来提炼你所选择的结果

  3. 在后台进行获取,不影响UI

  4. 直接更新一个对象在持久话存储区中,避免不必要的获取。

在真正开始前,你可以先看看之前写过的一片博客。在里面简单介绍了 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类型

  1. NSManagedObjectResultType: Returns managed objects (default value).

  2. NSCountResultType: Returns the count of the objects that match the fetch
    request.

  3. NSDictionaryResultType: This is a catch-all return type for returning the results of different calculations.

  4. 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)")
      }
  }
  1. 首先创建检索地点对象的读取请求,接下来指定结果类型是 DictionaryResultType

  2. 创建一个 NSExpressionDescription 来请求和,并且给他取名为sumDeals,这个就可以从字典中读取出他的结果。

  3. NSExpressionDescription一个具体的NSExpression,你想要的和函数。最后你需要说明返回的数据类型。所以将其设置为Integer32AttributeType

  4. 告诉你声明的fetch request 设置 propertiesToFetch属性为你创建的NSExpression

  5. 最终执行这个 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.predicatefetchRequest.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)")
}

来看看上边的代码都做了些什么

  1. 通过 entityName 来实例化一个 NSFetchRequest

  2. 通过刚刚 NSFetchRequest 对象来实例化一个NSAsynchronousFetchRequest ,对了还有一个回调闭包 。你要获取的 venues 被包含在 NSAsynchronousFetchRequest 的 finalResult 属性中。

  3. 你还需要去执行这个异步的获取。

  4. 在执行完了 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!


dowhilenet
654 声望10 粉丝