2014年11月14日金曜日

CloudKitでページング読み込み

皆様こんばんわ。
京都はすっかり朝夕冷え込んできまして、紅葉シーズン真っ盛りです。

さて、色々とスッといかないCloudKitさんですが、懲りずにまだやってます。

というわけで、今回はページング処理についてです。
前回も書きましたが、CloudKitはCKDatabase.perfomQueryにて一回で取ってこれる行数が100行までとなってます。少ない行数のクエリでしたらコチラのほうが楽ちんですが、普通はコレでは色々困るかと思います。

で、100件毎ページングしながら取ってくる、、という処理にするのですが、もちろん上のCKDatabase.perfomQueryでも自前でページング処理を書けば動きます。が、そんなの面倒くさい、、ということでCKQueryOperationの登場です。

簡単なサンプルを書きます。
------------------------------------------------------
// CKQueryCursorをクラス変数に持ちます
var cursor:CKQueryCursor?
// レコードを保持する配列
var records:[CKRecord] = []

func record_search() {
    // データベース接続です
    var db: CKDatabase! = CKContainer.defaultContainer().publicCloudDatabase

    // 条件です。
    var p:NSPredicate = NSPredicate(format: "id > 0")!

    // TableNameからレコードを取得します
    let q : CKQuery = CKQuery(recordType: "TableName", predicate: p)

    // CKQueryOperationの定義です
    var queryop : CKQueryOperation
    
    // ★ここがポイント、カーソルをチェックします。
    if self.cursor == nil{
        // カーソルがnil、すなわち最初のクエリ
        queryop = CKQueryOperation(query: q)
    } else {
        // カーソルがある状態(次のレコードを取ってくる)
        queryop = CKQueryOperation(cursor: self.cursor)
    }
    
    // 一回に取ってくる件数
    queryop.resultsLimit = 100
    
    // フェッチ毎の処理を書きます
    queryop.recordFetchedBlock = {(record:CKRecord!) -> Void in
       // 配列にCKRecordをAdd
        self.records.append(record)
    }

    // すべてのフェッチが終わった後の処理を書きます
    queryop.queryCompletionBlock = { (cursor:CKQueryCursor!, error:NSError!) -> Void in
        // カーソル状態を、クラス変数のカーソルに保持します。
        self.cursor = cursor
        // UITableViewのリロード処理
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
            self.tblView.reloadData()
         })
    }        

    // dbに上記のCKQueryOperationをセットします。
    self.db?.addOperation(queryop)
}

------------------

という感じで、上記のようなメソッドを初期処理で呼ぶと最初のレコード100件が、そして、たとえばTableViewの下端に差し掛かった時に同じメソッドを読んでやれば次の100件を再度積み上げて行ってくれる、、、といった感じです。どうでしょう割とスッキリですね。

しかし、いまこのソースをテストしてみたら、Maxまでカーソルをフェッチしたらまた最初からレコードを繰り返し読んできて再度積み上げてくれる、、、という感じになってしまってます。今日はもう帰って晩御飯を作らないといけないのでまた次回までにバグを直します。ではさようなら。


4 件のコメント:

  1. I think you provided the answer I was looking for, although I don't understand a word in japanese. But thanks anyway dude and congratulations for the nice blog.

    返信削除
  2. Just out of gratitude I want to share back with you the modifications I did in your original Code. I made some little adaptations in order to be able to compile the code in XCode 7 and Swift 2. My intent was to fetch all records from iCloud. I have more than 1k records but iCloud was giving me only 100 although after some modifications on my code I was getting 300 records, but still far from the 1000 records I needed to get. So the code bellow can give you all records in your iCloud container. I hope it might be of help for somebody looking for a similar solution:
    -----------------------
    // CKQueryCursorをクラス変数に持ちます
    var cursor:CKQueryCursor?
    // レコードを保持する配列
    var records:[CKRecord] = []

    func record_search() {
    // データベース接続です
    let db: CKDatabase! = CKContainer.defaultContainer().publicCloudDatabase

    // 条件です。
    //let p:NSPredicate = NSPredicate(format: "id > 0")
    let p = NSPredicate(value: true)

    // TableNameからレコードを取得します
    //let q : CKQuery = CKQuery(recordType: "TableName", predicate: p)
    let q = CKQuery(recordType: "Message", predicate: p)

    // CKQueryOperationの定義です
    var queryop : CKQueryOperation

    // ★ここがポイント、カーソルをチェックします。
    if self.cursor == nil {
    // カーソルがnil、すなわち最初のクエリ
    queryop = CKQueryOperation(query: q)
    } else {
    // カーソルがある状態(次のレコードを取ってくる)
    queryop = CKQueryOperation(cursor: self.cursor!)
    }

    // 一回に取ってくる件数
    //queryop.resultsLimit = 100

    // フェッチ毎の処理を書きます
    queryop.recordFetchedBlock = { (record: CKRecord!) -> Void in
    // 配列にCKRecordをAdd
    //self.records.append(record)
    self.saveFetchedRecord(record) //I'm not copying the records but saving them directly to a local db with Datacore.

    }

    // すべてのフェッチが終わった後の処理を書きます
    queryop.queryCompletionBlock = { [weak self] (cursor : CKQueryCursor?, error : NSError?) -> Void in

    print(self!.records.count)

    self!.cursor = cursor
    if cursor != nil {
    self?.record_search() // runs this function recursively until all record have been downloaded, i.e., cursor == nil
    }

    //Do UI Updates here
    dispatch_async(dispatch_get_main_queue(), { () -> Void in
    self!.showTwoLatestMsg() // This updates my UI with the just saved data
    })
    }

    // dbに上記のCKQueryOperationをセットします。
    db?.addOperation(queryop)
    }

    返信削除
  3. ありがとうございました

    返信削除
  4. You're welcome. Do you need a freelancer? Contact me: estevesdeveloper@gmail.com

    返信削除