2015年6月29日月曜日

共通モデルのライブラリ化を試作(機能追加編)

共通で使えるモデルクラスをライブラリ化する第1段として、記録カードのモデルクラスを作る話は終わりました。しかし、簡単な検索機能を急に追加したので、続きとして紹介します。

 

今回のモデルクラスは、まだ機能が不足気味です。どんな機能を追加したら良いのか、作り終わっても考え続けていました。やはり検索機能が必要だと感じ、そこだけ集中して検討したのです。凝った機能は実現が非常に難しいと判断し、とりあえず簡単に実現できる方法を追加することにしました。

検索機能では、検索の種類をどれだけ増やせるかが重要です。あらかじめ用意する決められた方法では、やはり限界があります。それよりも、検索で使う判定条件をアプリ側から与えて、モデルクラス側では、与えられた判定条件を実行する形が良いでしょう。

このような仕組みの実現には、判定条件を関数として与える方法が有効です。アプリ側で関数を用意し、その関数をメソッドに渡して、モデルクラス側で実行します。

ただし、なんでも出来るわけではありません。与えられる関数の仕様や、関数の渡し方によって、自ずと限界が生じます。凝った仕組みは大変なので、単純な仕組みを選びました。特定の1項目の値だけを見て、条件に適合しているのか判断する関数です。

関数の仕様は単純です。項目のデータ型がIntの場合は、関数のデータ型が「(Int) -> Bool」となります。つまり、Int型の値を受け取って、何か処理をし、Bool値を返すという関数です。他のデータ型も同様で、データ型がStringであれば、関数のデータ型が「(String) -> Bool」となります。これらの関数は真偽を判定する役割なので、ここでは判定関数と呼びましょう。判定関数の結果がtrueのレコードだけ、検索結果に含めます。

 

具体的なSwiftコードを見てみましょう。データ型が4種類なので、4つのメソッドを追加しましたが、まずはInt型だけ。

// ======================================== 条件検索
func findIntF(rFldIdx:Int, _ rFunc:(Int) -> Bool) -> [Int] {
    let iDataIdx: Int = cAryDataIdx[rFldIdx]
    var iAryRecIdx: [Int] = [Int]()
    for i in 0..<cRecCount {   // i=RecIdx
        let iInt: Int = cAryDataInt[iDataIdx][i]
        if rFunc(iInt) {
            iAryRecIdx.append(i)
        }
    }
    return iAryRecIdx
}

4つとも同じような処理なので、Int型だけ説明します。ソースコードも短く、単純な処理です。

引数は、項目インデックス番号FldIdxと判定関数だけです。アプリ側では、FldIdxとして予め用意した変数(定数)名を使うので、FldIdxの範囲の検査は付けていません。

処理の最初では、指定された項目の値が入っている配列の位置を探します。FldIdxからDataIdxを求めるだけです。位置が見つかったので、全部の値を最初から見ていきます。レコード数と同じ回数だけループする処理になります。

ループの直前で、判定関数でtrueとなったレコードの番号RecIdxを入れる配列iAryRecIdxを用意します。ループ内では、データの入った配列cAryDataIntから、項目の値を取り出し、変数iIntに入れます。その値を判定関数に与えて、判定結果がtrueであれば、レコード番号(i=RecIdx)を配列iAryRecIdxに追加します。

ループの最後には、レコード番号の配列iAryRecIdxを返すだけです。もし全部がfalseなら、空の配列を返します。

 

残りの3つのメソッドは、次のとおりです。

// 残り3つのメソッド
func findFloatF(rFldIdx:Int, _ rFunc:(Float) -> Bool) -> [Int] {
    let iDataIdx: Int = cAryDataIdx[rFldIdx]
    var iAryRecIdx: [Int] = [Int]()
    for i in 0..<cRecCount {   // i=RecIdx
        let iFloat: Float = cAryDataFloat[iDataIdx][i]
        if rFunc(iFloat) {
            iAryRecIdx.append(i)
        }
    }
    return iAryRecIdx
}
func findBoolF(rFldIdx:Int, _ rBool:Bool) -> [Int] {
    let iDataIdx: Int = cAryDataIdx[rFldIdx]
    var iAryRecIdx: [Int] = [Int]()
    for i in 0..<cRecCount {   // i=RecIdx
        let iBool: Bool = cAryDataBool[iDataIdx][i]
        if iBool == rBool {
            iAryRecIdx.append(i)
        }
    }
    return iAryRecIdx
}
func findStrF(rFldIdx:Int, _ rFunc:(String) -> Bool) -> [Int] {
    let iDataIdx: Int = cAryDataIdx[rFldIdx]
    var iAryRecIdx: [Int] = [Int]()
    for i in 0..<cRecCount {   // i=RecIdx
        let iStr: String = cAryDataStr[iDataIdx][i]
        if rFunc(iStr) {
            iAryRecIdx.append(i)
        }
    }
    return iAryRecIdx
}

見て分かるように、Bool型だけは関数を使っていません。2種類の値しかないので、指定した値と同じかどうか調べるしかないですから。残りのメソッドの処理内容はInt型と同じなので、説明は省略します。

 

続いて、使用例の紹介です。今回も、iOS実験専用アプリから抜き出しました。

検索するためには、判定関数が必要です。掲載した例では、判定関数をクロージャとして用意し、変数に入れています。それを検索メソッドに渡すだけです。

// アプリ側で検索機能を使ってみる

// 指定された項目の値が、500以下のレコードを検索
let iFuncI1: (Int) -> Bool = { (rInt:Int) -> Bool in return (rInt < 500) }
let iAryRecIdx: [Int] = modelRecCard25.findIntF(fNumber_i, iFuncI1)

// 指定された項目の値が、変数に入っている標準値より大きなレコードを検索
var iIntStd: Int = 100
let iFuncI3: (Int) -> Bool = { (rInt:Int) -> Bool in return (rInt > iIntStd) }
let iAryRecIdx3: [Int] = modelRecCard25.findIntF(fNumber_i, iFuncI3)

// 指定された項目の値が、trueのレコードを検索(関数は不要)
let iAryRecIdxB: [Int] = modelRecCard25.findBoolF(fSpMember_b, true)

// 指定された項目の文字列で、先頭が「C」のレコードを検索
let iFuncS2: (String) -> Bool = { (rStr:String) -> Bool in return rStr.hasPrefix("C") }
let iAryRecIdxS2: [Int] = modelRecCard25.findStrF(fName_s, iFuncS2)

// 指定された項目の文字列で、どこかに「g」があるレコードを検索
let iFuncS3: (String) -> Bool = { (rStr:String) -> Bool
    in if let iRange = rStr.rangeOfString("g") { return true }; return false }
let iAryRecIdxS3: [Int] = modelRecCard25.findStrF(fName_s, iFuncS3)

これらの例のように、関数さえ作れれば、かなり様々な判定が可能です。1つの項目値で判定可能な内容なら、たいていのことができると思います。

アプリ側では、検索条件を満たしたレコード番号RecIdxの配列しか受け取りません。そのレコード番号をもとに、他のgetメソッドを使って、該当するレコードの項目値を取得します。

モデルクラスに多くのデータが入っている場合は、全部の項目値を受け取るよりも、少ないデータ量で済みます。その点が、この検索メソッドの価値でしょう。

 

複数の項目を使った判定も、作ろうと思えば追加できます。しかし、実装は相当に大変そうです。2つの項目値でも、データ型の組み合わせが多く、その組み合わせを全部用意するのか、それともデータ型を指定できる工夫を用意するのか、どちらにしても難しいでしょう。

1つの項目だけを使う検索メソッドなら簡単に作れるので、現実的な選択肢として選びました。これなら、4つのメソッドを用意するだけで済みますから。

 

今回の検索機能は、簡単な機能の追加でしたが、幅広く使えるメソッドになりました。また、何か思い浮かんだら、モデルクラスに機能を追加したいと思います。

 

(使用開発ツール:Xcode 6.3.2, SDK iOS 8.3)

0 件のコメント:

コメントを投稿