美文网首页iOSiOS数据持久化swift
大宝剑之精挑细选——CoreData(四)

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

作者: StrongX | 来源:发表于2015-04-24 02:41 被阅读14559次

    大宝剑的挑选是庄严而神圣的,必须一丝不苟的完成,防止出现自己约的炮含泪也得打完的惨剧。
    (回到正题)当我们读取数据时,并不是每份数据都需要的,比如说有时候我们只想要女生的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,

    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>

    • //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.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。
    • 因为返回类型是一个字典形,所以在这里要设置key为sumDeals,也就是这里的name。
    • 这里统计的是specialCount的和,** forFunction**填的是“sum:”其实这里有好多可以填的,点进这个方法,你可以看到好多~好多~好多~~其实这里就是给设定一个统计的方式。
    • 设定** .propertiesToFetch**属性。
    • 发出请求,返回一个字典型。key就是我们前面设定的sumDeals。

    运行一下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 根据选择结果,确定判断条件,这三个断言在之前已经写过了。
    • //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 确定返回内容,我们这里返回更新的个数
    一共更新了30条

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

    喝杯奶茶缓缓~~~~~

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

    相关文章

      网友评论

      • iamsea:感谢 :flushed:
      • StrongX:@swifter 亲~请看我的上一篇文章coreData(三),里面详细描述创建一个coredata栈,应该能够解决你的问题。
      • 0294b96fe4db:大神 怎么在已经创建的项目添加coreData 呢 大神 网上资料 不多 我指的是swift项目

      本文标题:大宝剑之精挑细选——CoreData(四)

      本文链接:https://www.haomeiwen.com/subject/hteufttx.html