2015年2月3日火曜日

AnyObjectの欠点を補うクラス(使用例編)

AnyObjectの欠点を補うクラスの続きです。前の投稿では3つのクラスを作りましたので、今回は使い方を紹介します。もともとAnyObjectへ入れて使うのが作成目的のため、紹介するのはAnyObjectへ入れる例だけとなります。

 

最初の例では、AnyObjectの配列に入れてみましょう。一般的なアプリの中での使用例ではなく、動作を見るための使用例となります。

// ========== 使用例:配列
// 変数の準備
var iIntObj : IntObj = IntObj(12)
var iFloatObj : FloatObj = FloatObj(12.34)
var iBoolObj : BoolObj = BoolObj(true)
var iStr : String = "sample"
println("変数:i=\(iIntObj),f=\(iFloatObj),b=\(iBoolObj)")
// 配列の準備
var aryAnyObj : [AnyObject] = [AnyObject]()
aryAnyObj.append(iIntObj)
aryAnyObj.append(iFloatObj)
aryAnyObj.append(iBoolObj)
aryAnyObj.append(iStr)
// 型ごとの処理
for iObj in aryAnyObj {
    switch iObj {
    case is IntObj:
        let iInt : Int = (iObj as IntObj).getValueF()
        println("IntObj=\(iInt)")
    case is FloatObj:
        let iFloat : Float = (iObj as FloatObj).getValueF()
        println("FloatObj=\(iFloat)")
    case is BoolObj:
        let iBool : Bool = (iObj as BoolObj).getValueF()
        println("BoolObj=\(iBool)")
    case is String:
        let iStr9 : String = iObj as String
        println("String=\(iStr9)")
    default: break
    }
}

まず最初、AnyObjectへ入れる前に、それぞれのクラスの変数として用意します。入れたい値を渡して、単純にインスタンス生成するだけです。

生成後には値を表示しています。3つのクラスとも、Printableプロトコルに準拠させました。そのため、printlnで変数名を指定するだけで、値を表示できます。デバッグするときなどに便利ですね。

続いてAnyObject配列を用意して、最初に生成した変数を順番に追加するだけです。

最後はfor文で、配列からAnyObjectを順番に取り出します。今回は、データ型の判定にswitch文を使いました。caseに続くis演算子でクラス名を指定すると、簡単に判定できます。クラス名だけ書けば良いので、Intなどの基本的な型と同じように使えるのが良い点です。

ただし値を取り出すときには、getValueFメソッドが必要です。AnyObjectを該当クラスにキャストした後、そのメソッドで値を取り出します。それぞれのgetValueFメソッドでは、戻り値として型が指定されるので、Swiftの型チェックが有効に活かされます。

 

上記コードの実行結果は、次のとおりです。

変数:i=12,f=12.34,b=true
IntObj=12
FloatObj=12.34
BoolObj=true
String=sample

is演算子できちんと判定され、getValueFメソッドで値が取り出されていることが分かります。また最初の行では、plintlnで変数名だけから値が表示されるのも確認できます。

 

次は、Dictionary内のAnyObjectへ入れる場合の例です。これもまた、一般的なアプリの中での使用例ではなく、動作を見るサンプルとしての使用例となります。

// ========== 使用例:Dictionary
// 変数の準備
var iIntObj : IntObj = IntObj(12)
var iFloatObj : FloatObj = FloatObj(12.34)
var iBoolObj : BoolObj = BoolObj(true)
var iStr : String = "sample"
println("変数:i=\(iIntObj),f=\(iFloatObj),b=\(iBoolObj)")
// Dictionaryの準備
var iDic : Dictionary<String,AnyObject> = [String:AnyObject]()
iDic["aInt"] = iIntObj
iDic["aFloat"] = iFloatObj
iDic["aBool"] = iBoolObj
iDic["aString"] = iStr
println("dic=\(iDic)")
// シリアライズ(書き出し)
let iPathDocs : AnyObject = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let iPathFile : String = iPathDocs[0].stringByAppendingPathComponent("dicAnyObjPlus.plist")
let iResult = NSKeyedArchiver.archiveRootObject(iDic, toFile:iPathFile)
if !iResult { println("ERROR:ファイル書き失敗:dicAnyObjPlus") }
// シリアライズ(読み込み)
let iFileMgr : NSFileManager = NSFileManager.defaultManager()
if !(iFileMgr.fileExistsAtPath(iPathFile)) { println("ERROR:ファイルが存在せず"); return }
let iDic2 = NSKeyedUnarchiver.unarchiveObjectWithFile(iPathFile) as Dictionary<String,AnyObject>
let iInt2 : Int = (iDic2["aInt"] as IntObj).getValueF()
let iFloat2 : Float = (iDic2["aFloat"] as FloatObj).getValueF()
let iBool2 : Bool = (iDic2["aBool"] as BoolObj).getValueF()
let iStr2 : String = iDic2["aString"] as String
println("dic:i=\(iInt2),f=\(iFloat2),b=\(iBool2),s=\(iStr2)")

最初の変数は、前の例と同じです。

続いて、それらをAnyObjectのDictionaryへ、キーを指定しながら入れます。入れた後で、Dictionaryごとprintln文で表示しています。

後半の処理は、Dictionaryのシリアライズです。Dictionaryのまま、Documentフォルダへ書き出します。

以前は、DictionaryをNSDictionaryへキャストして、writeToFileメソッドで書き出しました。しかし今回は、その方法が使えません。writeToFileメソッドが対応しているのは、限られたデータ型のみだからです。今回のクラスは、その限られたデータ型に含まれていないので、NSKeyedArchiverクラスのarchiveRootObjectメソッドを使いました。

最後の部分は、書き出したファイルが存在しているかチェックし、存在している場合にだけ読み込む処理です。読み込んだデータを別なDictionaryに代入して、個々の値を取り出してます。どの値の取り出しでも、Dictionary内のAnyObjectを各クラスにキャストし、getValueFメソッドで値を取得するのが基本です。

 

上記コードの実行結果は、次のようになりました。

変数:i=12,f=12.34,b=true
dic=[aString: sample, aFloat: 12.34, aInt: 12, aBool: true]
dic:i=12,f=12.34,b=true,s=sample

シリアライズは成功し、読み込んだ結果も正常でした。

 

3つのクラスを実際に作ってみると、使い方は意外に簡単だと分かりました。

Int型とIntObjクラスで使い方を比べたとき、is演算子では同じような書き方となります。println文での使い方も似たようなものです。変数への代入や取り出しでメソッドが必要となりますが、それほど面倒ではありません。これでis演算子の判定結果が期待どおりになるのですから、意外に価値はあると思います。

一番最初に考えたのは、1つだけのクラスとして作り、入れたデータ型を情報として持つ方法でした。この方法を採用していたら、今回のように使いやすくはなりませんでした。前回書いたように遠回りしましたが、最終的には良い結果が得られたのではないでしょうか。

これぐらい簡単に使えるなら、以前にライブラリ化した環境設定機能でも使いたくなりました。そのうち、改良しようと思います。

実は、今回のクラスが、Int型よりも良いかもしれない点が1つだけあります。アップルの技術資料には、AnyObjectはオブジェクトのインスタンス用と書いてあり、Int型の値が入れられるのは保証されていません。Swiftのバージョンアップにより、突然と使えなくなる可能性も残っています。そんな場合でも、今回のクラスはインスタンスを生成するので、使い続けられるというわけです。まあ、Int型が入れられなくなる状況には、ならないと思いますけど。

 

AnyObjectの欠点を補うクラスができたことで、AnyObjectの代わりにAnyを使う必要性がほとんどなくなりました。Anyを使うのは、Anyが本当に必要な場合だけでしょう。これからは、今までよりも安心してAnyObjectが使えます。

 

今回の3つのクラスは、独自ライブラリの中で、もっとも基本的な機能を実現するクラスと位置づけられます。そのため、既存の「BC_Base.swift」に入れました。このファイルには、以前に紹介した「数秒後に自動で消えるメッセージ表示」で作ったクラスも入れてあります。

今回作ったクラスも、新しくアプリを作るとき、真っ先にコピーする独自ライブラリとして活躍してくれるでしょう。

 

(使用開発ツール:Xcode 6.1.1, SDK iOS 8.1)

0 件のコメント:

コメントを投稿