獲取你所須要的數據html
經過predicates
來提煉你所選擇的結果ios
在後臺進行獲取,不影響UIgit
直接更新一個對象在持久話存儲區中,避免沒必要要的獲取。數據庫
在真正開始前,你能夠先看看以前寫過的一片博客。在裏面簡單介紹了 CoreData 中獲取數據。express
CoreData的增刪查改swift
當你有了大概的瞭解以後,咱們來慢慢的體會。數組
咱們的會在一個demo的基礎上來進行。你能夠在這裏下載初始項目安全
也許你還須要瞭解NSPredicate1閉包
NSpredicate2app
在以前的章節中,你已經學習到了經過建立一個存在的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
咱們來經過後兩種方法來存儲咱們獲取的數據
打開咱們項目中的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,來看看界面上的數據發生了什麼改變。
別小看了 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.
咱們來看看這些概念在實際中是怎麼應用的吧。
打開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() }
咱們來經過另一種方式來獲取 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 來看看數據是否發生了改變。
接下來呢咱們來讓獲取到的結果進行一些運算。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 }
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 中的選項來看看效果。
若是你已經遠遠獲得這一點,有兩個好消息和壞消息(而後是更多的好消息)。你已經學到了不少關於你能夠用一個簡單的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 初始化,因此呢,這裏咱們將給它一個空的數組。以致於咱們的視圖能夠默認加載沒有數據的視圖。
有時候,你須要從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!