iOSiOS数据持久化swift

大宝剑之精挑细选——CoreData(四)

2015-04-24  本文已影响14559人  StrongX

大宝剑的挑选是庄严而神圣的,必须一丝不苟的完成,防止出现自己约的炮含泪也得打完的惨剧。
(回到正题)当我们读取数据时,并不是每份数据都需要的,比如说有时候我们只想要女生的QQ~~~~~男生的QQ这时候给我我还得一个一个来挑选,又偏了~~

  • 参考书籍:CORE DATA by Tutorials。

这一篇的内容:

我们知道读取数据之前我们必须创建一个读取请求,这个读取请求就是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:


一如既往的,我们每一个篇章都会有一个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,

4.png
选择我们新建的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>

<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通过设定这个属性,你可以通过查询返回不同的结果,这是所有的返回类型:


返回数量

来到我们的删选界面,在这个界面的最上节,我们将返回不同价格的奶茶店的数量。
打开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**

同样的道理,我们用同样的方法获取了价格为“$$”的个数。代码如下:
<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.png

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。

运行一下APP:

一共12次交易记录
接下来的功能是这样的:在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 根据选择结果,确定判断条件,这三个断言在之前已经写过了。

好的我们来到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,因为我们传过来的判断条件参数只有一个是有值。

来让我们运行一下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>

因为我们进行的是异步读取,所以刚进入程序时[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联系起来。
一共更新了30条

这一篇的内容大概就是这样了,总结一下,其实就是通过设定返回类型,和删选条件来读取不同的数据,然后还有异步获取,最后讲了批量更新。

喝杯奶茶缓缓~~~~~

初始模板以上传github:https://github.com/superxlx/BubbleTea
源代码已上传github:https://github.com/superxlx/CoreDataTest4

上一篇 下一篇

猜你喜欢

热点阅读