大寶劍的挑選是莊嚴(yán)而神圣的,必須一絲不茍的完成翘骂,防止出現(xiàn)自己約的炮含淚也得打完的慘劇壁熄。
(回到正題)當(dāng)我們讀取數(shù)據(jù)時,并不是每份數(shù)據(jù)都需要的碳竟,比如說有時候我們只想要女生的QQ~~~~~男生的QQ這時候給我我還得一個一個來挑選草丧,又偏了~~
- 參考書籍:CORE DATA by Tutorials。
- 默認(rèn)有swift基礎(chǔ)莹桅。
- 默認(rèn)已閱讀上一篇內(nèi)容昌执。
這一篇的內(nèi)容:
- 按條件查詢返回數(shù)量
- 按條件查詢
- 排序
- 異步查詢
- 批量同步
我們知道讀取數(shù)據(jù)之前我們必須創(chuàng)建一個讀取請求,這個讀取請求就是NSFetchRequest诈泼,這里有四種方法來創(chuàng)建一個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來初始化呜师,關(guān)于data model中的這個屬性娶桦,在后面會提到。
- //4 這種方法同樣使用了data model中的fetch request汁汗,同時設(shè)定了一個斷言,來限定最后的輸出結(jié)果知牌。
一如既往的祈争,我們每一個篇章都會有一個Demo,今天的這個Demo較為復(fù)雜角寸,由書籍CORE DATA by Tutorials提供菩混,來看一下這個Demo:
在這個界面會顯示所有的奶茶店忿墅,點擊Filter按鈕以后會跳轉(zhuǎn)到刪選條件的界面。
在個界面會顯示刪選條件沮峡,刪選條件包括價格區(qū)間疚脐,$表示十美元以內(nèi),$$表示出售100美元的奶茶店邢疙,$$$表示出售1000美元奶茶的店棍弄。刪選條件還包括距離,是否有用戶評價疟游,還有根據(jù)字母排序........
來看一下文件結(jié)構(gòu):
其中Stats.seift呼畸、Venue.swift、Location.swift颁虐、PriceInfo.swift蛮原、Category.swift都是Entity生成的類,其他的文件都不是特別復(fù)雜聪廉,就不多說瞬痘。注意一下seed.json文件,這個文件提供了紐約地區(qū)真實的奶茶店情況~
開始完善我們的Demo板熊,我們首先要做的就是將所有的奶茶店顯示在TableView中框全,而所有的奶茶店則是存儲在Venue這個Entity中,所以我們要創(chuàng)建一個Fetch Request讀取Venue中的所有數(shù)據(jù)干签,至于這些數(shù)據(jù)什么時候存進去的津辩?打開我們的AppDelegate.swift,我們在進入程序的最開始已經(jīng)將seed.json中的所有數(shù)據(jù)都存起來了容劳,至于怎么存的喘沿,這不在今天的考慮當(dāng)中。
打開Model.xcdatamodeld竭贩,長按Add Entity蚜印,不要放開鼠標(biāo),選擇Add Fetch Request留量,這樣我們在data model中添加了一個Fetch Request窄赋,
選擇我們新建的Fetch Request,將Fetch All選擇為Venue楼熄,這一步講Fetch Request與Entity聯(lián)系起來:
打開ViewConreoller.swift忆绰,先是添加頭文件
import CoreData
添加存放讀取數(shù)據(jù)的數(shù)組,和讀取數(shù)據(jù)的請求:
var fetchRequest: NSFetchRequest!
var venues: [Venue]!
在viewDidLoad方法中添加一下代碼:
<pre><code>
//1
fetchRequest = coreDataStack.model.fetchRequestTemplateForName("FetchRequest")
//2
fetchAndReload()
</code></pre>
- //1 這個就是我們前面提到的第三種創(chuàng)建Fetch Request的方法可岂,這種方法從data model中創(chuàng)建的Fetch Request來進行初始化错敢,而在模板中我們已經(jīng)吧這個Fetch Request與Entity聯(lián)系起來了。
- //2 這個方法我們在TableView中顯示數(shù)據(jù)缕粹,當(dāng)然我們還沒有添加這個方法稚茅,接下來就是添加這個方法:
<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>
這個方法就不進行解釋了纸淮,前面的篇章中已經(jīng)反復(fù)寫過很多次了。
我們已經(jīng)把所有的奶茶店的數(shù)據(jù)都讀取到[Venue]數(shù)組中了峰锁,接下來就是在TableView中顯示這些數(shù)據(jù)萎馅,以下代碼:
<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>
代碼簡單就不進行解釋了,有問題的同學(xué)虹蒋,可以留言提問糜芳,好的,來讓我們運行下程序吧魄衅。
NSFetchReuqest是多功能的,他的返回值不僅僅可以是對象晃虫,他同樣可以查詢數(shù)據(jù)的數(shù)量皆撩,平均值、最大值哲银、最小值······他有一個屬性叫做resultType通過設(shè)定這個屬性扛吞,你可以通過查詢返回不同的結(jié)果,這是所有的返回類型:
- NSManagedObjectResultType:這是默認(rèn)的選項荆责,返回了所有的對象滥比。
- NSCountResultType:返回所有對象的數(shù)量。
- NSDictionaryResultType:對查詢結(jié)果進行統(tǒng)計做院,包括求和盲泛,最大值,最小值等等等键耕。好多好多寺滚,要不要列出來?算來還不列了屈雄,點進xcode的解釋里頭村视,全列出來了。
- NSManagedObjectIDResultType: 返回所有對象的ID酒奶,這個方法現(xiàn)在已經(jīng)被淘汰了蚁孔,淘汰了就不多說了。
返回數(shù)量
來到我們的刪選界面讥蟆,在這個界面的最上節(jié),我們將返回不同價格的奶茶店的數(shù)量纺阔。
打開FilterViewController.swift瘸彤,添加以下代碼:
import CoreData
var coreDataStack:CoreDataStack!
返回ViewController.swift,在prepareForSegue函數(shù)中添加以下代碼:
filterVC.coreDataStack = coreDataStack
將暫存器傳過去~~而不用重新新建一個笛钝。
再回到FilterViewController.swift质况,我們首先創(chuàng)建一個懶惰屬性:
<pre><code>
lazy var cheapVenuePredicate: NSPredicate = {
var predicate = NSPredicate(format: "priceInfo.priceCategory == %@", "$")
return predicate
}()
</code></pre>
懶惰屬性的意思就是在使用到這個屬性的時候才會去創(chuàng)建這個變量愕宋,而不是在最開始創(chuàng)建。那么這個懶惰屬性的作用是什么呢结榄,這個懶惰屬性可以限定我們的查詢結(jié)果中贝,在這個懶惰屬性中,就是限定priceInfo.priceCategory是“$”臼朗,繼續(xù)在這個頁面中添加以下代碼:
<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邻寿,當(dāng)這么指定以后,讀取的返回結(jié)果就是對象的數(shù)量视哑。
指定了刪選條件绣否,就是我們之前設(shè)定的斷言。 - //3 讀取結(jié)果挡毅,結(jié)果返回一個NSNumber的數(shù)組蒜撮,這個數(shù)組只有一個值,所以我們獲取個數(shù)保存在result[0]中跪呈。
將讀取到的結(jié)果顯示在界面上段磨。
同樣的道理,我們用同樣的方法獲取了價格為“$$”的個數(shù)耗绿。代碼如下:
<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>
我們現(xiàn)在來運行一下程序來看一下結(jié)果苹支,如下圖所示:
我們已經(jīng)計算出了“$”和"$$"的價格的奶茶店的個數(shù),事實上我們有一種更好的方式來計算對象的個數(shù)缭乘,如以下代碼所示:
<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>
同樣我們設(shè)定了一個斷言沐序,來限制了查詢結(jié)果,但是在這里我們沒有設(shè)定他的返回就結(jié)果堕绩,而是用到了這樣一個方法
func countForFetchRequest(request: NSFetchRequest, error: NSErrorPointer) -> Int
這個方法同樣會返回查詢結(jié)果對象的數(shù)量策幼。來看一下最終的運行結(jié)果吧:
wonderful!奴紧!~~~特姐,一共27家“$”級別奶茶店,兩家"$$"級別奶茶店黍氮,一家"$$$"級別奶茶店唐含。
呼呼~~~休息休息,我去喝杯奶茶再回來~
X只能喝一杯十元的奶茶了沫浆,很想知道那千元奶茶店捷枯,賣的是啥~~~
好的我們來完成第二個模塊。
對各種服務(wù)進行統(tǒng)計专执,就是我們前面提到的第三種返回類型淮捆,真的是很黃很暴力哈。添加以下代碼:
<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,將返回類型設(shè)定為 . DictionaryResultType攀痊。
- 因為返回類型是一個字典形桐腌,所以在這里要設(shè)置key為sumDeals,也就是這里的name苟径。
- 這里統(tǒng)計的是specialCount的和案站,** forFunction**填的是“sum:”其實這里有好多可以填的,點進這個方法棘街,你可以看到好多~好多~好多~~其實這里就是給設(shè)定一個統(tǒng)計的方式蟆盐。
- 設(shè)定** .propertiesToFetch**屬性。
- 發(fā)出請求蹬碧,返回一個字典型舱禽。key就是我們前面設(shè)定的sumDeals。
運行一下APP:
接下來的功能是這樣的:在Fiters界面選擇刪選條件以后恩沽,點擊右上方的Search按鈕誊稚,返回上一屆面,按條件顯示對象,那我們第一步要做的就是將選擇的條件返回上一界面,再上一界面的TableView中顯示符合條件的對象繁涂,我們用協(xié)議來傳遞參數(shù)揪利,添加協(xié)議:
<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按鈕,返回界面以前執(zhí)行在viewConreoller中的方法** filterViewController**,并將參數(shù)傳過去。
添加刪選條件參數(shù)變量,前面提到了兩種刪選條件變量類型狼电,NSSortDescriptor和NSPredicate:
<pre><code>
weak var delegate: FilterViewControllerDelegate?
var selectedSortDescriptor: NSSortDescriptor?
var selectedPredicate: NSPredicate?
</code></pre>
前面我們已經(jīng)刪選過"$","$$","$$$"三個價位的奶茶店的數(shù)量,現(xiàn)在我們來顯示符合條件的奶茶店弦蹂,和之前的區(qū)別在哪里肩碟,區(qū)別就是前面我們的查詢返回結(jié)果是NSFetchRequestResultType.CountResultType,而現(xiàn)在則是默認(rèn)的凸椿,返回對象即可削祈,而我們之前寫的刪選條件則是一樣的,在
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
中添加選擇結(jié)果:
<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 根據(jù)選擇結(jié)果脑漫,確定判斷條件髓抑,這三個斷言在之前已經(jīng)寫過了。
- //2 這個只是給選擇的項目打個勾优幸,而已~~~~
好的我們來到viewController.swift來實現(xiàn)一下代理方法吨拍,并添加代理:
添加代理:
class ViewController: UIViewController,FilterViewControllerDelegate{
在prepareForSegue方法中添加(關(guān)于協(xié)議,就不解釋了~~不懂的可以留言~):
filterVC.delegate = self
好的网杆,實現(xiàn)方法:
<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 先將兩個參數(shù)初始化為nil羹饰,因為我們傳過來的判斷條件參數(shù)只有一個是有值握爷。
- //2 用if let來判斷到底哪個參數(shù)是有值的。
- //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”按鈕奈搜,結(jié)果如果所示:
其他的判斷條件也是只差刪選的斷言就可以進行一樣的刪選功能了:
500米以內(nèi),判斷條件如下:
<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>
異步獲取,在獲取大量數(shù)據(jù)的時候秋秤,可能會使程序無法響應(yīng)宏粤,用戶的操作,這個就很糟糕灼卢,如果可以異步獲取的話绍哎,那么再獲取數(shù)據(jù)的同時,用戶還可以進行操作芥玉,這就顯的十分友好蛇摸。
返回 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 這個指的是在完成查詢以后進行的操作灿巧,完成數(shù)據(jù)讀取以后當(dāng)然是更新tableview赶袄。
因為我們進行的是異步讀取,所以剛進入程序時[Venue]為空抠藕,這樣初始化TableView的話饿肺,會使程序崩潰,最簡單的解決方法就是這樣:
[Venue] = []
要進行異步讀取還有最后一步要做盾似,打開我們的CoreDataStack.swift文件將
context = NSManagedObjectContext()更改為
context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
好的敬辣,我們再把程序運行一下雪标,你會發(fā)現(xiàn)運行結(jié)果與之前一模一樣,這是因為數(shù)據(jù)數(shù)量較少的緣故溉跃,當(dāng)數(shù)據(jù)規(guī)模增大的時候則會顯現(xiàn)出很大的區(qū)別村刨。
批量更新,更新的時候我們不能說全部都重新存一遍哈撰茎,數(shù)量少的時候還好說嵌牺,數(shù)量多的時候這樣做就太糟糕了。
<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 批量更新其實很簡單哈龄糊,先是創(chuàng)建一個NSBatchUpdateRequest和Entity聯(lián)系起來逆粹。
- //2 通過batchUpdate.propertiesToUpdate屬性來確定要更新的內(nèi)容。
- //3 確定更新的對象炫惩,時候影響到數(shù)據(jù)庫僻弹。
- //4 確定返回內(nèi)容,我們這里返回更新的個數(shù)
這一篇的內(nèi)容大概就是這樣了他嚷,總結(jié)一下蹋绽,其實就是通過設(shè)定返回類型,和刪選條件來讀取不同的數(shù)據(jù),然后還有異步獲取筋蓖,最后講了批量更新蟋字。
喝杯奶茶緩緩~~~~~
初始模板以上傳github:https://github.com/superxlx/BubbleTea
源代碼已上傳github:https://github.com/superxlx/CoreDataTest4