環境設定機能の独自ライブラリ化で必要なので、Anyのシリアライズを機能確認しました。そうしたら、また面白いことが見付かりました。これらか紹介する内容としては、AnyとAnyObjectの違いになるので、掲げたようなタイトルにしてあります。
AnyObjectではIntやFloatなどの区別ができないので、Anyに切り替えようと、Anyを含んだDictionaryのシリアライズを試しました。シリアライズができるようになれば、AnyObjectをAnyに切り替えられるからです。
シリアライズには、NSKeyedArchiverクラスのarchiveRootObjectメソッドを使いました。コーディングしたら、いきなりコンパイルエラーです。iOS実験専用アプリで機能確認してますから、AnyObjectと比較する意味で、同じようなコードをAnyObjectでも作りました。AnyObjectのほうはエラーが出ませんし、実行させると正常に動きます。
前回の投稿で使ったソースコードに加えて試していますが、追加部分だけ掲載すると意味不明でしょうから、関連するコードだけ残したものが、次のSwiftコードです。
// Dictionaryをファイルとして保存
// 変数の準備
let aryTypeName : [String] = ["Int", "Float", "Bool", "String", "Error!!!"]
let iInt : Int = 5
let iFloat : Float = 3.3
let iBool : Bool = false
let iStr : String = "test"
var dicAny : Dictionary<String,Any> = [String:Any]()
var dicAnyObj : Dictionary<String,AnyObject> = [String:AnyObject]()
dicAny[aryTypeName[0]] = iInt
dicAny[aryTypeName[1]] = iFloat
dicAny[aryTypeName[2]] = iBool
dicAny[aryTypeName[3]] = iStr
dicAnyObj[aryTypeName[0]] = iInt
dicAnyObj[aryTypeName[1]] = iFloat
dicAnyObj[aryTypeName[2]] = iBool
dicAnyObj[aryTypeName[3]] = iStr
// Dictionaryをファイルとして保存
let iPathDocs : AnyObject = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let iPathFile1 : String = iPathDocs[0].stringByAppendingPathComponent("dicAny.plist")
let iPathFile2 : String = iPathDocs[0].stringByAppendingPathComponent("dicAnyObj.plist")
//let iResult1 = NSKeyedArchiver.archiveRootObject(dicAny, toFile:iPathFile1) // コンパイルエラー
//if !iResult1 { println("ERROR:ファイル書き失敗:Any") }
let iResult2 = NSKeyedArchiver.archiveRootObject(dicAnyObj, toFile:iPathFile2)
if !iResult2 { println("ERROR:ファイル書き失敗:AnyObj") }
上記のコメント化した2行のうち、1行目がコンパイルエラーです。エラーメッセージをから推測すると、シリアライズ処理ではAnyを扱えないようです。つまり、Anyを含んだインスタンスなどは、シリアライズの対象外になるということでしょう。
前回の投稿で書いたとおり、Anyを含んだDictionaryは、NSDictionaryへキャストできませんでした。今回のシリアライズも、根本原因は同じだと思います。AnyはSwiftになってから用意されたものなので、NSDictionaryやNSKeyedArchiverがサポートしていないのでしょう。
通常のシリアライズが使えないと判明したものの、別な可能性を探るために、Anyの機能確認を続けてみました。
とりあえず、AnyとAnyObjectで、相互に代入を試しました。これが可能であれば、解決方法が幅広く考えられますので。試したのは、以下のSwiftコードです。
// AnyとAnyObjectでの代入
// 変数の準備
let iInt : Int = 5
let iAnyInt : Any = iInt
let iAnyObjInt : AnyObject = iInt
// 代入
let iAny : Any = iAnyObjInt // ビルド時にエラー
//let iAnyObj : AnyObject = iAnyInt // コンパイルエラー(エディタ上で表示)
コメント化した行が、コンパイルエラーが出た部分です。Any変数の値はAnyObject変数に代入できませんでした。テキストエディタへ入力した状態で、コンパイルエラーになりました。
もう片方の、AnyObject変数の値をAny変数に代入するコードは、エディタ上でエラーがでないものの、ビルドするときにエラーが出てビルドが失敗します。通常の書き方では、どちらの方向へも代入できないようです。
ここまでの仕様確認の結果から、どんどんと道が閉ざされていると感じます。いったん整理してみましょう。
まず、as演算子でIntなどのデータ型が正しく判定できるのは、Anyだけです。判定が必要なDictionaryでは、Anyを使うしか選択肢がありません。
続いて、Anyを含むDictionaryのシリアライズです。通常の方法では、コンパイルエラーが出て書けません。また、NSDictionaryへキャストしてもエラーが出ます。別な方法を用意して、ファイルに保存する必要があります。
その保存方法に使えないかと考えたのが、Any変数とAnyObject変数を用いた相互の代入です。しかし、どちらの方向への代入でも、エラーが出て使えませんでした。
おそらく次のような形なら、ファイルへの保存と復元が実現可能だと思います。
AnyObjectならシリアライズ可能なので、ファイル保存にAnyObjectを用います。ただし、いったんAnyObjectに入れてしまうと、データ型の判定ができません。データ型の情報もファイルへ保存して、Anyへ復元するときに利用するしかないでしょう。
AnyとAnyObjectは相互に代入できないので、通常のデータ型の変数へ代入してから、もう片方のAnyまたはAnyObjectへ代入する方法を使います。ファイル保存時に用いるAnyからAnyObjectへの代入では、Anyがデータ型を判定できるので、その判定結果を用いて、中間変数のデータ型を選びます。続く中間変数からAnyObjectへの代入では、何も判定する必要はありません。
ファイルからの復元時に用いるAnyObjectからAnyへの代入でも、通常のデータ型の変数を通します。AnyObjectではデータ型を正しく判定できないので、保存しておいたデータ型の情報を使って、中間変数のデータ型を選びます。この中間変数を経由してAnyへ代入すれば、データ型が判定可能なAny変数として復元できます。
Dictionaryは、キーと値が対になったデータ構造です。データ型の情報を、どのように保存するのか決めなければなりません。1つの実現方法としては、元のキーから自動生成できる別なキー(元のキーの後ろに決められた文字列を追加するとか)を使って、一緒に入れることも考えられます。しかし、これだとfor文で回すときに、本来のキーかどうかを区別する必要があり、処理が面倒になってお勧めできません。同じキーの別なDictionaryとして用意したほうが、変更への柔軟性が高まるでしょう。まあ、どんな方法を採用するにしても、あまり作りたくない処理ですね。
今回、AnyとAnyObjectの違いという話題で、シリアライズに関する機能確認に始まり、相互の代入も調べてみました。
最初に考えていたのとは違って、AnyとAnyObjectは細かな違いがありました。また、相互に代入できないのも驚きでした。せめて代入ができれば、両方の違いを上手に使い分けられるので、非常に残念です。何か別な方法で代入できれば良いのですが。
肝心の機能拡張機能の独自ライブラリですが、改良は延期です。今のままでも普通に使うのぶんには困りませんので、当分は今のままで使いたいと思います。もっとスマートな解決方法が見付かったら、改良するかもしれません。
(使用開発ツール:Xcode 6.1.1, SDK iOS 8.1)
0 件のコメント:
コメントを投稿