以前に行なったAnyObjectの仕様確認で、IntやFloatを入れたときにis演算子が区別できないと判明しました。そのことを含めて、AnyとAnyObjectの違いや使い分けも検討しました。やはりAnyObjectのほうが便利であり、is演算子によるInt判定の欠点さえ解消されれば、今のところ最高の汎用データ型になれるはずです。
というわけで、欠点を補うためのクラスを作りました。そのクラスと、考慮した点を紹介します。
前回の投稿では、1つのクラスを用意して、IntやFloatの値を入れる形を考えました。要望を満たせる方法ではありますが、よくよく考えるとベストな方法ではありません。もっと良い方法を思いつきました。
その方法とは、Intだけを入れるクラス、Floatだけを入れるクラス、Boolだけを入れるクラスと、3つのクラスを用意する方法です。この方法なら、どのデータ型を入れているのか判断する必要はありません。入っているデータ型の判定処理が不要になることで、使い勝手は格段に良くなるはずです。
3つのクラスで実現する方法を思いついたのは、そもそも別なことを考えていた結果です。Intをクラスに変換できれば、AnyObjectに入れた後でis演算子を使うとき、そのクラスの名前で判定できるのではと思ったからです。しかし、いろいろ探してみたものの、そのような方法は見付かりませんでした(もし知っていたら教えてください)。仕方がないので、自分で作ればと考えたとき、実はそれこそが一番良い解決方法だと気付いたわけです。このような回り道する思考過程は、良くありますね。
非常に簡単なクラスですが、いつもどおりに全体設計を考えます。具体的に考えたほうが理解しやすいので、とりあえずIntを例に。
このクラスは、iOSが提供する様々なAPIと組み合わせて使うことになります。その場合、最も親となるNSObjectを継承したクラスとして作るのがベストです。一部のAPIと組み合わせて使えないというトラブルが防げますから。
値の入ってない状態を作らないのも大事なことです。初期化の際に値を指定する形とします。他に、値を設定したり、値を取得する機能も必要です。
このクラスのインスタンスをAnyObjectに入れた状態で、全体をシリアライズする機会も多いでしょう。そのためには、このクラス自体もシリアライズに対応する必要があります。
また、デバッグ目的で使う際の使い勝手を考慮し、インスタンスの文字列化にも対応させましょう。以上をまとめると、次のようになります。
AnyObjectを補うクラス(Int編)の要件
・iOSの各種APIと組み合わせやすいように、NSObjectを継承
・初期化するときに値を指定
・値を変更したり取得する機能
・シリアライズ可能に
・文字列化にも対応
こうして整理した要件を、もう少し具体的な機能として変換すると、次のようになります。
AnyObjectを補うクラス(Int編)の具体的な要素
・クラスの親子関係:NSObjectを継承
・クラス内の変数:入れるデータ(Int)だけ
・初期化:値(Int)を指定して
・メソッド:値(Int)の取得
・メソッド:値(Int)の設定
・シリアライズ可能に:NSCodingプロトコル準拠
・文字列化:Printableプロトコルを上書き
以上の要素を満たすように、コーディングするだけです。
まずはクラスの入れ物から。NSObjectを継承して、NSCodingに準拠するので、次のようなSwiftコードになりました。
// AnyObjectを補うクラス(Int編)
class IntObj : NSObject, NSCoding {
var aoInt : Int = -99999
init(_ rInt:Int) {
super.init()
aoInt = rInt
}
}
クラス変数はIntだけで、宣言するときにダミーの値を入れています。初期化の処理では、入れる値を受け取って、変数に設定するだけです。簡単ですね。ちなみにaoはAnyObjectの略で、特別な意味はありません。
クラス名は少し悩みましたけど、Intをオブジェクトにしたという意味で、IntObjとしてみました。どんなクラスなのか、何となく伝わると思います。
続いて、値の取得と設定のメソッドです。これも非常に簡単です。
// 値の設定と取得
func setValueF(rInt:Int) {
aoInt = rInt
}
func getValueF() -> Int {
return aoInt
}
あまりに簡単すぎて、機能については説明することがありません。ただし、メソッド名に関してですが、getIntFとはせずに、getValueFとしました。他の2つのクラスと、メソッド名を統一したかったからです。
次は、文字列化を実現するため、Printableプロトコルへの準拠です。親クラスのNSObjectgが既に準拠しているため、上書きする形になります。
// Printableプロトコルを上書き
override var description: String { return aoInt.description }
ここでは、descriptionを使わずにString(aoInt)とも書けます。しかし、実現する機能に近い書き方が良いと考え、descriptionを使いました。
最後は、シリアライズを可能にするNSCodingプロトコルへの準拠です。これも短く書け、次のようなSwiftコードになりました。
// NSCodingプロトコル準拠
func encodeWithCoder(rCoder:NSCoder) {
rCoder.encodeInteger(self.aoInt, forKey:"aoInt")
}
required init(coder rDecoder:NSCoder) {
self.aoInt = rDecoder.decodeIntegerForKey("aoInt")
}
整数型を扱うエンコードやデコードは、それぞれ複数ありました。Swiftには整数型が何種類もあるからでしょう。API資料で比べ、Int型を対象にしたものを選びました。
クラスのSwiftコードは、以上で終わりです。全体が短いソースコードなので、今回だけは全部含めた形で再掲します。なお、ソースコード内の並び順は、説明した順番とは違っています。
// AnyObjectを補うクラス(Int編)
class IntObj : NSObject, NSCoding {
var aoInt : Int = -99999
init(_ rInt:Int) {
super.init()
aoInt = rInt
}
// NSCodingプロトコル準拠
func encodeWithCoder(rCoder:NSCoder) {
rCoder.encodeInteger(self.aoInt, forKey:"aoInt")
}
required init(coder rDecoder:NSCoder) {
self.aoInt = rDecoder.decodeIntegerForKey("aoInt")
}
// 値の設定と取得
func setValueF(rInt:Int) {
aoInt = rInt
}
func getValueF() -> Int {
return aoInt
}
// Printableプロトコルを上書き
override var description: String { return aoInt.description }
}
全体としても、かなり短いソースコードです。もともと機能の少ないクラスですし、対応したプロトコルも少しだけですから。
Float型のクラスの場合も、Int型とほぼ同じです。Swiftコードは、次のようになりました。
// AnyObjectを補うクラス(Float編)
class FloatObj : NSObject, NSCoding {
var aoFloat : Float = -9999.9
init(_ rFloat:Float) {
super.init()
aoFloat = rFloat
}
// NSCodingプロトコル準拠
func encodeWithCoder(rCoder:NSCoder) {
rCoder.encodeFloat(self.aoFloat, forKey:"aoFloat")
}
required init(coder rDecoder:NSCoder) {
self.aoFloat = rDecoder.decodeFloatForKey("aoFloat")
}
// 値の設定と取得
func setValueF(rFloat:Float) {
aoFloat = rFloat
}
func getValueF() -> Float {
return aoFloat
}
// Printableプロトコルを上書き
override var description: String { return aoFloat.description }
}
見てのとおりInt型とほぼ同じなので、説明は不要でしょう。
Int型と同様に、NSCodingプロトコル準拠では、浮動小数点型を扱うエンコードとデコードがそれぞれ複数ありました。API資料を見て、Float型となっているメソッドを選んでいます。
続いて、Bool型のクラスです。次のようなSwiftコードになりました。
// AnyObjectを補うクラス(Bool編)
class BoolObj : NSObject, NSCoding {
var aoBool : Bool = false
init(_ rBool:Bool) {
super.init()
aoBool = rBool
}
// NSCodingプロトコル準拠
func encodeWithCoder(rCoder:NSCoder) {
rCoder.encodeBool(self.aoBool, forKey:"aoBool")
}
required init(coder rDecoder:NSCoder) {
self.aoBool = rDecoder.decodeBoolForKey("aoBool")
}
// 値の設定と取得
func setValueF(rBool:Bool) {
aoBool = rBool
}
func getValueF() -> Bool {
return aoBool
}
// Printableプロトコルを上書き
override var description: String { return aoBool.description }
}
これもまた、Int型とほぼ同じです。
以上、3つのクラスを作りましたが、どれも最低限の機能しか持たせていません。いつものように、何か追加が必要になったら、そのときに追加すればよいと思います。
長くなったので、これらクラスの使用例は次回にて。
(使用開発ツール:Xcode 6.1.1, SDK iOS 8.1)
0 件のコメント:
コメントを投稿