共通で使えるモデルクラスをライブラリ化する第1段として、記録カードのモデルクラスを作る話を紹介しました。今回は、その後の状況を報告します。
記録カードのモデルクラスが一段落したので、2つ目のモデルクラスを作りました。記録カードと少し似ている、キー付きカードのモデルクラスです。記録カードにキーが追加されたと考えて、ほぼ間違いありません。記録カードはArreyに相当し、キー付きカードはDictionaryに相当すると考えれば、中身を想像しやすいと思います。
記録カードを作った経験があるので、テストコードも含めて、おおよそ1/3の時間で作り終わりました。クラスに限らず何かのソフトを作る場合には、コーディングしている時間よりも、仕様を考えている時間のほうが多いです。
2つ目のキー付きカードは、記録カードという前例があります。そのため、どんな形で内部データを保存するのか、どんなメソッドがあればよいのか、どんな検査機能を付けなければならないのか、などで悩みませんでした。また、テストコードも似た形で作れるので、その点でも悩みませんでした。まさに、スイスイと作れたという感じです。
肝心なのは、内部データの持ち方ですね。最初からAnyかAnyObjectと決めていたので、どちらにするか決めるだけです。4つの基本データ型だけでなく、将来はクラスのインスタンスも持てるようにしたいはずです。ほとんどのクラスがNSObjectのサブクラスですから、それと相性の良いAnyObjectが適しているでしょう。というわけでAnyObject配列を選びました。
キー付きというモデルなのも関係しているのか、記録カードのモデルクラスよりも、少しスッキリした内部構造に仕上がりました。AnyObject配列を選んで正解だったようです。
2つ目のモデルクラスの結果が良かったので、記録カードのモデルクラスも、内部データを持つ配列をAnyObject型に変更してみました。
今までは、データ型ごとに配列を用意したので、配列が4つもあります。それを、1つのAnyObject配列に置き換えるだけです。余計なインデックス変換も不要になり、コードが少し短くなります。
変更前のモデルクラスは別名で保存しておき、変更作業を進めました。実際に作業してみると、30分ぐらいで終了です。内部の仕様だけが変更なので、テストコードはそのまま使えます。コンパイルエラーを全部消して実行すると、一発で通りました。変更完了です。
よく考えると、変更する箇所は多いものの、変更の内容自体は単純でした。4つの配列を消して、1つのAnyObject配列を追加します。インデックスを変換している箇所では、変換している1行を削除し、新しいインデックスに置き換えるだけでした。また、AnyObject配列からデータを読む箇所では、それぞれのデータ型へキャストする指定を追加します。
このようにパターン化できる作業しかないため、かなり慎重に進めても、30分ぐらいの時間で終わりました。当然ですが、一番最初に作ったのは、新しいAnyObject配列の追加と、インデックスの使い方を説明するコメントです。このコメントを見ながら作業するので、間違いを起こさず更新できたのでしょう。
そのコメント行を紹介します。まずは、変更前のコメントから。
// ======================================== 3つのインデックスがある
// RecIdx:レコードの並び順を示すインデックス
// FldIdx:項目(フィールド)の並び順を示すインデックス:この値からDataIdxを得て、それでデータへアクセスする
// DataIdx:データ配列内で、どの行(位置)に入っているかを示すインデックス:FldIdxをキーにして、cAryDataIdxから得る
// ============================== 次のように利用する
// cAryName[FldIdx] -> String (項目名)
// cAryType[FldIdx] -> ZmType (データ型)
// cAryDataIdx[FldIdx] -> DataIdx
// cAryDataXXX[DataIdx][RecIdx] -> Data (項目値:4種類のデータ型)
// ========================================
インデックスを変換するDataIdxが使われています。続いて、変更後のコメントです。
// ======================================== 2つのインデックスがある
// RecIdx:レコードの並び順を示すインデックス
// FldIdx:項目(フィールド)の並び順を示すインデックス
// ============================== 次のように利用する
// cAryName[FldIdx] -> String (項目名)
// cAryType[FldIdx] -> ZmType (データ型)
// cAryDataAny[FldIdx][RecIdx] -> AnyObject (項目値:中身は4種類のデータ型)
// ========================================
3つのインデックスから、2つのインデックスへと変更されたのが、もっとも大きな点でしょう。DataIdxがなくなり、FldIdxで直接指定できる形になりました。また、項目値のデータ型が、変数上はAnyObjectだけに変わっています。
変更したメソッドのうち、主なものも紹介します。まずは、getメソッドから。
// 値の取得(変更前)
func getDataIntF(rRecIdx:Int, _ rFldIdx:Int) -> Int {
if cAryType[rFldIdx] != .ZmInt { zSendErrMsgF("ERROR:ZMR_GDI:項目定義のデータ型がIntでない"); return -999 }
if rRecIdx < 0 || rRecIdx >= cRecCount { zSendErrMsgF("ERROR:ZMR_GDI:レコード位置が範囲外"); return -999 }
let iDataIdx: Int = cAryDataIdx[rFldIdx]
let iInt: Int = cAryDataInt[iDataIdx][rRecIdx]
return iInt
}
// 値の取得(変更後)
func getDataIntF(rRecIdx:Int, _ rFldIdx:Int) -> Int {
if cAryType[rFldIdx] != .ZmInt { zSendErrMsgF("ERROR:ZMR_GDI:項目定義のデータ型がIntでない"); return -999 }
if rRecIdx < 0 || rRecIdx >= cRecCount { zSendErrMsgF("ERROR:ZMR_GDI:レコード位置が範囲外"); return -999 }
let iInt: Int = cAryDataAny[rFldIdx][rRecIdx] as! Int
return iInt
}
変更後の処理では、インデックスを変換している行が消えて、メソッドが1行分だけ短くなりました。ソースコード全体を眺めても、たいして短くならなかったというのが感想です。内部構造としては、余計なインデックス変換がなくなった点が一番大きいでしょう。
AnyObject型の配列cAryDataAnyから、データを取り出す際にはキャストが必要です。でも、キャストのために数文字を追加するだけですから、あまり変わっていない印象ですね。
getメソッドと違って、setメソッドは変更ありません。Intなどのデータ型の値を、AnyObject配列へ入れるだけですから、特別な指定は不要です。
続いて、データを保存する配列の初期化です。
// データ用の配列の初期化(変更前)
// 項目ごとに配列を生成して、その位置(インデックス番号)を記憶する
private func setupArrayF() {
for i in 0..<cFldCount { // i=FldIdx
switch cAryType[i] {
case .ZmInt :
cAryDataIdx.append(cAryDataInt.count) // count値がインデックス値より1つ大きいので、作成前に保存する
cAryDataInt.append([Int]())
case .ZmFloat :
cAryDataIdx.append(cAryDataFloat.count)
cAryDataFloat.append([Float]())
case .ZmBool :
cAryDataIdx.append(cAryDataBool.count)
cAryDataBool.append([Bool]())
case .ZmStr :
cAryDataIdx.append(cAryDataStr.count)
cAryDataStr.append([String]())
}
}
}
// データ用の配列の初期化(変更後)
// データ保存用の配列を初期化して、項目ごとに配列を生成する
private func setupArrayF() {
cAryDataAny = [[AnyObject]]() // データ保存用の配列を初期化
for i in 0..<cFldCount { // i=FldIdx
cAryDataAny.append([AnyObject]()) // 項目ごとに、空の配列を追加
}
}
保存場所を指すインデックス値DataIdxを保存する必要がなくなったのと、配列が1つになったので、かなり短くなりました。ちなみに、これだけ短くなったのは、ここの1箇所だけです。
このような感じで、無事に内部データをAnyObject配列に更新できました。
ただ更新しただけでは、とくにメリットはありません。しかし、内部配列がAnyObject型になったことで、いろいろなデータ型を簡単に入れる準備ができました。4つのデータ型とは違うデータ型へも、苦労せずに対応できそうです。
もちろん、以前の内部配列の構造でも、新しいデータ型へは対応できます。AnyObject配列を追加して、そこへ入れれば良いだけです。でも、データ保存用の配列が少しずつ増えていくのは、あまり良い形とは言えません。スッキリした構造から、どんどんと離れていきますから。
今回の変更は、将来の機能拡張の準備として、大きな意味があったと思います。今後も、このモデルクラスを改良して、少しずつ良くしていく予定です。
(使用開発ツール:Xcode 6.3.2, SDK iOS 8.3)
0 件のコメント:
コメントを投稿