大宝剑的挑选是庄严而神圣的,必须一丝不苟的完成,防止出现自己约的炮含泪也得打完的惨剧。
(回到正题)当我们读取数据时,并不是每份数据都需要的,比如说有时候我们只想要女生的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按钮以后会跳转到删选条件的界面。
2.png
在个界面会显示删选条件,删选条件包括价格区间,$表示十美元以内,$$表示出售100美元的奶茶店,$$$表示出售1000美元奶茶的店。删选条件还包括距离,是否有用户评价,还有根据字母排序........
来看一下文件结构:
3.png
其中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联系起来:
与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>
我们现在来运行一下程序来看一下结果,如下图所示:
1.png
我们已经计算出了“$”和"$$"的价格的奶茶店的个数,事实上我们有一种更好的方式来计算对象的个数,如以下代码所示:
<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
这个方法同样会返回查询结果对象的数量。来看一下最终的运行结果吧:
1.pngwonderful!!~~~,一共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
网友评论