大寶劍的挑選是莊嚴而神圣的,必須一絲不茍的完成,防止出現自己約的炮含淚也得打完的慘劇。
(回到正題)當我們讀取數據時,并不是每份數據都需要的,比如說有時候我們只想要女生的QQ~~~~~男生的QQ這時候給我我還得一個一個來挑選,又偏了~~
- 參考書籍:CORE DATA by Tutorials。
- 默認有swift基礎。
- 默認已閱讀上一篇內容。
這一篇的內容:
- 按條件查詢返回數量
- 按條件查詢
- 排序
- 異步查詢
- 批量同步
我們知道讀取數據之前我們必須創建一個讀取請求,這個讀取請求就是NSFetchRequest,這里有四種方法來創建一個NSFetchRequest
<pre><code>
//1
let fetchRequest1 = NSFetchRequest()
let entity = NSEntityDescription.entityForName("Person",
inManagedObjectContext: managedObjectContext!) fetchRequest1.entity = entity!
//2
let fetchRequest2 = NSFetchRequest(entityName: "Person")
//3
let fetchRequest3 = managedObjectModel.fetchRequestTemplateForName("peopleFR")
//4
let fetchRequest4 = managedObjectModel.fetchRequestFromTemplateWithName("peopleFR",
substitutionVariables: ["NAME" : "Ray"])
</code></pre>
Have a look:
- //1 //2這兩種就不多說了,前面的篇章都遇到過,就是通過不同方法初始化,然后指定了Entity。
- //3 這種方法用data model中的fetch request來初始化,關于data model中的這個屬性,在后面會提到。
- //4 這種方法同樣使用了data model中的fetch request,同時設定了一個斷言,來限定最后的輸出結果。
一如既往的,我們每一個篇章都會有一個Demo,今天的這個Demo較為復雜,由書籍CORE DATA by Tutorials提供,來看一下這個Demo:
在這個界面會顯示所有的奶茶店,點擊Filter按鈕以后會跳轉到刪選條件的界面。
在個界面會顯示刪選條件,刪選條件包括價格區間,$表示十美元以內,$$表示出售100美元的奶茶店,$$$表示出售1000美元奶茶的店。刪選條件還包括距離,是否有用戶評價,還有根據字母排序........
來看一下文件結構:
其中Stats.seift、Venue.swift、Location.swift、PriceInfo.swift、Category.swift都是Entity生成的類,其他的文件都不是特別復雜,就不多說。注意一下seed.json文件,這個文件提供了紐約地區真實的奶茶店情況~
開始完善我們的Demo,我們首先要做的就是將所有的奶茶店顯示在TableView中,而所有的奶茶店則是存儲在Venue這個Entity中,所以我們要創建一個Fetch Request讀取Venue中的所有數據,至于這些數據什么時候存進去的?打開我們的AppDelegate.swift,我們在進入程序的最開始已經將seed.json中的所有數據都存起來了,至于怎么存的,這不在今天的考慮當中。
打開Model.xcdatamodeld,長按Add Entity,不要放開鼠標,選擇Add Fetch Request,這樣我們在data model中添加了一個Fetch Request,
選擇我們新建的Fetch Request,將Fetch All選擇為Venue,這一步講Fetch Request與Entity聯系起來:
打開ViewConreoller.swift,先是添加頭文件
import CoreData
添加存放讀取數據的數組,和讀取數據的請求:
var fetchRequest: NSFetchRequest!
var venues: [Venue]!
在viewDidLoad方法中添加一下代碼:
<pre><code>
//1
fetchRequest = coreDataStack.model.fetchRequestTemplateForName("FetchRequest")
//2
fetchAndReload()
</code></pre>
- //1 這個就是我們前面提到的第三種創建Fetch Request的方法,這種方法從data model中創建的Fetch Request來進行初始化,而在模板中我們已經吧這個Fetch Request與Entity聯系起來了。
- //2 這個方法我們在TableView中顯示數據,當然我們還沒有添加這個方法,接下來就是添加這個方法:
<pre><code>
func fetchAndReload(){
var error: NSError?
let results = coreDataStack.context.executeFetchRequest(fetchRequest,error: &error) as! [Venue]?
if let fetchedResults = results { venues = fetchedResults
} else {
println("Could not fetch (error), (error!.userInfo)")
}
tableView.reloadData()
}
</pre></code>
這個方法就不進行解釋了,前面的篇章中已經反復寫過很多次了。
我們已經把所有的奶茶店的數據都讀取到[Venue]數組中了,接下來就是在TableView中顯示這些數據,以下代碼:
<pre><code>
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
}
</code></pre>
代碼簡單就不進行解釋了,有問題的同學,可以留言提問,好的,來讓我們運行下程序吧。
NSFetchReuqest是多功能的,他的返回值不僅僅可以是對象,他同樣可以查詢數據的數量,平均值、最大值、最小值······他有一個屬性叫做resultType通過設定這個屬性,你可以通過查詢返回不同的結果,這是所有的返回類型:
- NSManagedObjectResultType:這是默認的選項,返回了所有的對象。
- NSCountResultType:返回所有對象的數量。
- NSDictionaryResultType:對查詢結果進行統計,包括求和,最大值,最小值等等等。好多好多,要不要列出來?算來還不列了,點進xcode的解釋里頭,全列出來了。
- NSManagedObjectIDResultType: 返回所有對象的ID,這個方法現在已經被淘汰了,淘汰了就不多說了。
返回數量
來到我們的刪選界面,在這個界面的最上節,我們將返回不同價格的奶茶店的數量。
打開FilterViewController.swift,添加以下代碼:
import CoreData
var coreDataStack:CoreDataStack!
返回ViewController.swift,在prepareForSegue函數中添加以下代碼:
filterVC.coreDataStack = coreDataStack
將暫存器傳過去~~而不用重新新建一個。
再回到FilterViewController.swift,我們首先創建一個懶惰屬性:
<pre><code>
lazy var cheapVenuePredicate: NSPredicate = {
var predicate = NSPredicate(format: "priceInfo.priceCategory == %@", "$")
return predicate
}()
</code></pre>
懶惰屬性的意思就是在使用到這個屬性的時候才會去創建這個變量,而不是在最開始創建。那么這個懶惰屬性的作用是什么呢,這個懶惰屬性可以限定我們的查詢結果,在這個懶惰屬性中,就是限定priceInfo.priceCategory是“$”,繼續在這個頁面中添加以下代碼:
<pre><code>
func populateCheapVenueCountLabel() {
// $ fetch request
//1
let fetchRequest = NSFetchRequest(entityName: "Venue")
//2
fetchRequest.resultType = NSFetchRequestResultType.CountResultType
fetchRequest.predicate = cheapVenuePredicate
//3
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 places"
} else {
println("Could not fetch (error), (error!.userInfo)")
}
}
override func viewDidLoad() {
super.viewDidLoad()
populateCheapVenueCountLabel()
}
</code></pre>
- //1 新建了一個** NSFetchRequest**
- //2 指定了resultType為NSFetchRequestResultType.CountResultType,當這么指定以后,讀取的返回結果就是對象的數量。
指定了刪選條件,就是我們之前設定的斷言。 - //3 讀取結果,結果返回一個NSNumber的數組,這個數組只有一個值,所以我們獲取個數保存在result[0]中。
將讀取到的結果顯示在界面上。
同樣的道理,我們用同樣的方法獲取了價格為“$$”的個數。代碼如下:
<pre><code>
lazy var moderateVenuePredicate: NSPredicate = {
var predicate = NSPredicate(format: "priceInfo.priceCategory == %@", "$$")
return predicate
}()
func populateModerateVenueCountLabel() {
// $$ fetch request
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 places"
} else {
println("Could not fetch (error), (error!.userInfo)")
}
}
</code></pre>
我們現在來運行一下程序來看一下結果,如下圖所示:
我們已經計算出了“$”和"$$"的價格的奶茶店的個數,事實上我們有一種更好的方式來計算對象的個數,如以下代碼所示:
<pre><code>
lazy var expensiveVenuePredicate: NSPredicate = {
var predicate = NSPredicate(format: "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 {
println("Could not fetch (error), (error!.userInfo)")
}
thirdPriceCategoryLabel.text = "(count) bubble tea places"
}
</code></pre>
同樣我們設定了一個斷言,來限制了查詢結果,但是在這里我們沒有設定他的返回就結果,而是用到了這樣一個方法
func countForFetchRequest(request: NSFetchRequest, error: NSErrorPointer) -> Int
這個方法同樣會返回查詢結果對象的數量。來看一下最終的運行結果吧:
wonderful!!~~~,一共27家“$”級別奶茶店,兩家"$$"級別奶茶店,一家"$$$"級別奶茶店。
呼呼~~~休息休息,我去喝杯奶茶再回來~
X只能喝一杯十元的奶茶了,很想知道那千元奶茶店,賣的是啥~~~
好的我們來完成第二個模塊。
對各種服務進行統計,就是我們前面提到的第三種返回類型,真的是很黃很暴力哈。添加以下代碼:
<pre><code>
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 = NSAttributeType.Integer32AttributeType
//4
fetchRequest.propertiesToFetch = [sumExpressionDesc]
//5
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)")
}
}
</code></pre>
- //1 新建一個NSFetchRequest,將返回類型設定為 . DictionaryResultType。
- 因為返回類型是一個字典形,所以在這里要設置key為sumDeals,也就是這里的name。
- 這里統計的是specialCount的和,** forFunction**填的是“sum:”其實這里有好多可以填的,點進這個方法,你可以看到好多~好多~好多~~其實這里就是給設定一個統計的方式。
- 設定** .propertiesToFetch**屬性。
- 發出請求,返回一個字典型。key就是我們前面設定的sumDeals。
運行一下APP:
接下來的功能是這樣的:在Fiters界面選擇刪選條件以后,點擊右上方的Search按鈕,返回上一屆面,按條件顯示對象,那我們第一步要做的就是將選擇的條件返回上一界面,再上一界面的TableView中顯示符合條件的對象,我們用協議來傳遞參數,添加協議:
<pre><code>
protocol FilterViewControllerDelegate: class {
func filterViewController(filter: FilterViewController,didSelectPredicate predicate:NSPredicate?,sortDescriptor:NSSortDescriptor?)
}
</code></pre>
在Search方法中添加以下代碼
<pre><code>
@IBAction func saveButtonTapped(sender: UIBarButtonItem) {
delegate!.filterViewController(self, didSelectPredicate: selectedPredicate, sortDescriptor: selectedSortDescriptor)
dismissViewControllerAnimated(true, completion:nil)
}
</code></pre>
這段代碼很明顯就是在點擊Search按鈕,返回界面以前執行在viewConreoller中的方法** filterViewController**,并將參數傳過去。
添加刪選條件參數變量,前面提到了兩種刪選條件變量類型,NSSortDescriptor和NSPredicate:
<pre><code>
weak var delegate: FilterViewControllerDelegate?
var selectedSortDescriptor: NSSortDescriptor?
var selectedPredicate: NSPredicate?
</code></pre>
前面我們已經刪選過"$","$$","$$$"三個價位的奶茶店的數量,現在我們來顯示符合條件的奶茶店,和之前的區別在哪里,區別就是前面我們的查詢返回結果是NSFetchRequestResultType.CountResultType,而現在則是默認的,返回對象即可,而我們之前寫的刪選條件則是一樣的,在
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
中添加選擇結果:
<pre><code>
//1
let cell = tableView.cellForRowAtIndexPath(indexPath)!
switch cell {
// Price section
case cheapVenueCell:
selectedPredicate = cheapVenuePredicate
case moderateVenueCell:
selectedPredicate = moderateVenuePredicate
case expensiveVenueCell:
selectedPredicate = expensiveVenuePredicate
default:
println("default case"
}
//2
cell.accessoryType = .Checkmark
</code></pre>
- //1 根據選擇結果,確定判斷條件,這三個斷言在之前已經寫過了。
- //2 這個只是給選擇的項目打個勾,而已~~~~
好的我們來到viewController.swift來實現一下代理方法,并添加代理:
添加代理:
class ViewController: UIViewController,FilterViewControllerDelegate{
在prepareForSegue方法中添加(關于協議,就不解釋了~~不懂的可以留言~):
filterVC.delegate = self
好的,實現方法:
<pre><code>
func filterViewController(filter: FilterViewController, didSelectPredicate predicate:NSPredicate?, sortDescriptor:NSSortDescriptor?) {
//1
fetchRequest.predicate = nil
fetchRequest.sortDescriptors = nil
//2
if let fetchPredicate = predicate {
fetchRequest.predicate = fetchPredicate
}
if let sr = sortDescriptor {
fetchRequest.sortDescriptors = [sr]
}
//3
fetchAndReload()
}
</code></pre>
- //1 先將兩個參數初始化為nil,因為我們傳過來的判斷條件參數只有一個是有值。
- //2 用if let來判斷到底哪個參數是有值的。
- //3 最后一步更新一下界面。
來讓我們運行一下app,選擇“$”級別奶茶店,再點擊Search按鈕,不出意外,果然崩潰了,為什么呢,因為我們的fetchrequest是從data model獲取的,而從data model獲取的fetchrequest是不可更改的,而我們給他添加了一個判斷條件,所以崩潰了,解決方法很簡單,換一個fetchRequest不就好了,如以下操作:
<pre><code>
override func viewDidLoad() {
super.viewDidLoad()
// fetchRequest = coreDataStack.model.fetchRequestTemplateForName("FetchRequest")
fetchRequest = NSFetchRequest(entityName: "Venue")
fetchAndReload()
}
</code></pre>
我們再來運行下app,選擇“$$”,再點擊“Search”按鈕,結果如果所示:
其他的判斷條件也是只差刪選的斷言就可以進行一樣的刪選功能了:
500米以內,判斷條件如下:
<pre><code>
lazy var walkingDistancePredicate: NSPredicate = {
var pr = NSPredicate(format: "location.distance < 500")
return pr
}()
</code></pre>
有用戶評價,判斷條件如下:
<pre><code>
lazy var hasUserTipsPredicate: NSPredicate = {
var pr = NSPredicate(format: "stats.tipCount > 0")
return pr
}()
</code></pre>
按名字升序排序:
<pre><code>
lazy var nameSortDescriptor: NSSortDescriptor = {
var sd = NSSortDescriptor(key: "name",ascending: true,selector: "localizedStandardCompare:")
return sd
}()
</pre></code>
按距離升序排序:
<pre><code>
lazy var distanceSortDescriptor: NSSortDescriptor = {
var sd = NSSortDescriptor(key: "location.distance",ascending: true)
return sd
}()
</pre></code>
按照價格升序:
<pre><code>
lazy var priceSortDescriptor: NSSortDescriptor = {
var sd = NSSortDescriptor(key: "priceInfo.priceCategory", ascending: true)
return sd
}()
</pre></code>
最后一步就是在TableView選擇方法中初始化刪選條件,像這樣:
<pre><code>
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)!
switch cell {
// Price section
case cheapVenueCell:
selectedPredicate = cheapVenuePredicate
case moderateVenueCell:
selectedPredicate = moderateVenuePredicate
case expensiveVenueCell:
selectedPredicate = expensiveVenuePredicate
case offeringDealCell:
selectedPredicate = offeringDealPredicate
case walkingDistanceCell:
selectedPredicate = walkingDistancePredicate
case userTipsCell:
selectedPredicate = hasUserTipsPredicate
case nameAZSortCell:
selectedSortDescriptor = nameSortDescriptor
case nameZASortCell:
selectedSortDescriptor = nameSortDescriptor.reversedSortDescriptor as? NSSortDescriptor
case distanceSortCell:
selectedSortDescriptor = distanceSortDescriptor
case priceSortCell:
selectedSortDescriptor = priceSortDescriptor
default:
println("default case")
}
cell.accessoryType = .Checkmark
}
</code></pre>
異步獲取,在獲取大量數據的時候,可能會使程序無法響應,用戶的操作,這個就很糟糕,如果可以異步獲取的話,那么再獲取數據的同時,用戶還可以進行操作,這就顯的十分友好。
返回 viewcontroller.swift,在viewDidLoad()中添加以下代碼:
<pre><code>
//2
asyncFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest){
[unowned self] (result: NSAsynchronousFetchResult! ) -> Void in
self.venues = result.finalResult as! [Venue]
self.tableView.reloadData() }
//3
var error: NSError?
let results = coreDataStack.context.executeRequest(asyncFetchRequest,error: &error)
if let persistentStoreResults = results {
//Returns immediately, cancel here if you want
} else {
println("Could not fetch (error), (error!.userInfo)")
}
</code></pre>
- //3 這個指的是在完成查詢以后進行的操作,完成數據讀取以后當然是更新tableview。
因為我們進行的是異步讀取,所以剛進入程序時[Venue]為空,這樣初始化TableView的話,會使程序崩潰,最簡單的解決方法就是這樣:
[Venue] = []
要進行異步讀取還有最后一步要做,打開我們的CoreDataStack.swift文件將
context = NSManagedObjectContext()更改為
context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
好的,我們再把程序運行一下,你會發現運行結果與之前一模一樣,這是因為數據數量較少的緣故,當數據規模增大的時候則會顯現出很大的區別。
批量更新,更新的時候我們不能說全部都重新存一遍哈,數量少的時候還好說,數量多的時候這樣做就太糟糕了。
<pre><code>
//1
let batchUpdate = NSBatchUpdateRequest(entityName: "Venue")
//2
batchUpdate.propertiesToUpdate = ["favorite" : NSNumber(bool: true)]
//3
batchUpdate.affectedStores = coreDataStack.psc.persistentStores
//4
batchUpdate.resultType = .UpdatedObjectsCountResultType
//5
var batchError: NSError?
let batchResult = coreDataStack.context.executeRequest(batchUpdate,error: &batchError) as! NSBatchUpdateResult?
if let result = batchResult {
println("Records updated (result.result)")
} else {
println("Could not update (batchError), (batchError!.userInfo)")
}
</code></pre>
- //1 批量更新其實很簡單哈,先是創建一個NSBatchUpdateRequest和Entity聯系起來。
- //2 通過batchUpdate.propertiesToUpdate屬性來確定要更新的內容。
- //3 確定更新的對象,時候影響到數據庫。
- //4 確定返回內容,我們這里返回更新的個數
這一篇的內容大概就是這樣了,總結一下,其實就是通過設定返回類型,和刪選條件來讀取不同的數據,然后還有異步獲取,最后講了批量更新。
喝杯奶茶緩緩~~~~~
初始模板以上傳github:https://github.com/superxlx/BubbleTea
源代碼已上傳github:https://github.com/superxlx/CoreDataTest4