CoreData Fetch 浅入

Intermerdiate Fetching

  1. 获取你所须要的数据html

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

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

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

在真正开始前,你能够先看看以前写过的一片博客。在里面简单介绍了 CoreData 中获取数据。express

CoreData的增删查改swift

当你有了大概的了解以后,咱们来慢慢的体会。数组

咱们的会在一个demo的基础上来进行。你能够在这里下载初始项目安全

也许你还须要了解NSPredicate1闭包

NSpredicate2app

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!

相关文章
相关标签/搜索