2015年1月25日日曜日

AnyとAnyObjectの違い(位置付け編)

AnyとAnyObjectの違いが、どうしても気になって、ネットで調べ続けていました。忙しくて、そんなことをしている状況ではないのに。でも、そのおかげで、納得できる答えが見付かりました。というわけで、AnyとAnyObjectの位置付け編です。

 

前に調べたときは、両者の違いを中心に置いていました。今回は方針を変えて、Objective-Cとの関連を中心に検索しましたわけです。そうしたら意外にも簡単に、短期間で謎が解けました。やはり鍵は、Objective-Cにありましたね。

Objective-CとSwiftとを比較しながら分析しているブログの中に、大きなヒントを与えてくれたブログがいくつかありました。SwiftのAnyObjectは、Objective-Cのid型に相当するのではないかと推測しています。これこそ重要な視点だと思いました。このヒントと、仕様確認の結果を組み合わせることで、両者の違いや位置付けが、大まかにですが解明できました。以下が、その内容です。

 

Swiftは新しい言語ですが、既存のiOS APIを使っています。また、Objective-Cで書かれたソフトウェアとの連携も考慮しなければなりません。つまり、新しい言語ではありますが、過去のシガラミを多く抱えたまま出発しなければならなかったわけです。

Objective-Cでは、汎用的なデータ型としてidが使われています。このidをラッピングしてSwiftで使えるようにしたものが、AnyObjectだったのです。Swiftでもidを利用することで、iOSの多くのAPIがそのまま使えます。これは大きな利点です。

ただし、いつくかのブログで報告されているように、AnyObjectがidと完全に同じではないようです。idで使えた一部の機能が、AnyObjectでは使えなくなっているとのことです。ごくごく一部だけのようですが。実装し忘れたのか、仕様上の理由で付けられなかったのかは不明ですが、ずっと使えないままであれば、仕様上の理由で付けられなかった可能性が高いでしょう。まあ、設計方針により外したという可能性もありますけど。

 

続いて、Anyです。過去のシガラミと関係ない汎用データ型も必要と考え、用意したのがAnyと言えます。iOS APIとは互換性がないものの、関数でも何でも入れられるデータ型として、言語としては「なくてはならない存在」です。

もちろん理想としては、汎用的なデータ型が1だけの状況でしょう。過去のiOS APIでも使えて、何でも入れられるデータ型が唯一ある状況がベストです。おそらく、それが実現不可能だったから、Anyを用意して、AnyObjectと共存させる結果となったのでしょう。

 

iOS APIも古いままではありません。一部のAPIは、Swiftで使いやすいように拡張されています。

その1つがDictionaryです。NSDictionaryにキャストできることから、DictionaryはNSDictionaryを拡張したものだと判断できます。その拡張内容の中には、Anyを扱えるようにしたことも含まれるはずです。Anyを用意した以上、いろいろな場面で使える必要がありますから。

ただし、仕様確認で見付かったように、DictionaryにAnyを含めるとキャストできなくなります。拡張された機能を使ったときだけ、キャストできないというのは、なかなか上手い作り方だと思います。Anyを使わないケースがほとんどのため、多くの場合にキャストできて、古いAPIの機能が使えるようになっているわけですから。

 

アップルの技術資料には、AnyObjectはクラスのインスタンスを入れる目的で使い、Anyは関数も含めて何でも入れられるように書いてあります。

このように書いているのに、AnyObjectにはIntやFloatの値が入れられますし、とくにコンパイルエラーも出していません。おそらく、is演算子などの実行結果がAnyObjectでは期待どおり動かないことを知って、クラスのインスタンス用という役割に決めさせたのでしょう。一部に欠点がありつつも使えるままにしながら、形式上は推奨はしないという具合に。

 

以上のような状況が理解できると、AnyとAnyObjectの位置付けが理解できますし、使い方も大まかに決まってきます。

iOSアプリを作る上では、様々なAPIを使うのが必須です。また、たいていのアプリでは、バージョンアップの度に機能を拡張するでしょう。新しいAPIを使う状況に、いつなってもおかしくないはずです。そんなときAnyを使っていたら、使い始めるAPIで使えない状況が生じやすくなります。というわけで、基本的には、AnyではなくAnyObjectを使ったほうが良いでしょう。

ただし、AnyObjectに入れるデータ型に、IntやFloatなどの基本的なデータが含まれる場合は、Anyを検討する必要があります。AnyObjectに入れられないわけではないため、絶対にAnyを使うべきとは言えません。実現する機能に対して、期待どおりの動作が得られないときだけ、Anyを使うという判断で構わないと思います。AnyObjectにIntやFloatを入れたとき、期待どおりの動作が得られるか、仕様確認するのは必須となりますが。

 

IntやFloatなどを入れながら、iOSのAPIを使いたい場合もあるでしょう(まさに、私が作った環境設定機能が該当します)。このような場合には、何か別な解決策を考えないとダメです。

1つ思いついたのは、IntやFloatを専用クラスに入れる方法です。いろいろなデータ型を入れられるクラスを新しく作って、データ自体はそのクラスのインスタンスに持たせ、そのインスタンスをAnyObjectに入れます。AnyObjectから値を取り出すときは、用意したクラスのメソッドを用いる形になります。

用意するクラスの仕様ですが、IntやFloatなどの値を入れる変数を持ちます。そして、どのデータ型の値を持っているのかを、また別な変数として持ちます。そのクラスでは、持っているデータ型の種類を変更したり読み出すメソッドと、実際のデータを読み書きするメソッドで構成します。つまり、どのデータ型を入れたのか保持しているクラスというわけです。使う際に手間が少し増えるものの、問題を解決できる現実的な方法と言えるでしょう。

 

ここまで数回の投稿で、AnyとAnyObjectの違いを調べながら、並行して仕様確認の実験を何度も試しました。これで一応の結論が出たと思います。今後は、iOSのAPIが大きく変わらない限り、AnyではなくAnyObjectを中心に使いたいと思います。

0 件のコメント:

コメントを投稿