前回に引き続き、SwiftからCやObjective-Cの遺産を今回も活用します。前回はおもにC――厳密にはlibc――の機能をSwiftから使うにはどうしたらよいかを見ていきましたが、今回はObjective-C、つまりフレームワークをどう活用していくかを見ていくことにしましょう。
import(Cocoa | UIKit)
Objective-CではCにおけるlibc
に相当するのが、OS XではCocoa
、iOSではUIKit
です。ただしlibc
よりできることははるかに多彩です。たとえば、あるURLにアクセスしてそのコンテンツを表示するというのは、Webすらなかった時代に作られたlibc
では簡単には書けませんが、Cocoa
やUIKit
であればわずかこれだけです(リスト1)。
なんと、NSString()
という文字列を初期化するAPIに適切なパラメータを渡すだけで、ソケットを初期化し、HTTPプロトコルでサーバにアクセスし、その内容をGETしてくれるわけです。あまりに楽なので、スクリプト言語でプログラミングしているような感覚です(図1)。
この場合URLのコンテンツはテキストですが、JSONをパースする機能すら基本装備しています(リスト2)。
しかしパースしたJSONから特定のアイテムを抜き取りたいとなると、かなり面倒なことになります(リスト3)。
JSONをサポートする多くの言語でjson["ItemAttributes"]["Author"]
と一度に書けるところを、まずlet item = json["ItemAttributes"] as? NSDictionary
でitem
を取り出し、さらにlet author =item["Author"] as? NSString
とNSString
を取り出しという具合に、型が静的であるというSwiftの特徴がアダとなってしまっています。どうにかしてjson["Item Attributes"]["Author"]
と書く方法はないでしょうか? さらに可能ならJavaScriptのようにjson.ItemAttributes.Author
と書けないのでしょうか?
ラッパーのススメ
その試みがSwiftyJSONであり、拙作のSwift-JSONです。たとえばSwift-JSONならリスト3のコードは
と1行で済んでしまいます。さらにスキーマをclass
として実装すれば、リスト4のようにすら書けます。
SwiftyJSONやSwift-JSONはこれをどのように実現しているのでしょうか? ソースコード全体を読んでいただければ一目瞭然なのですが、SwifyJSONは1,163行、Swift-JSONは432行で紙幅にとても収まりません(2015年3月当時)。ここではSwift-JSONのキモだけ解説します。
Swift-JSONのインスタンス変数は、たった1つです。
これに対し、subscript
は2種類定義されています(リスト5、リスト6)。
つまり、json[0]
のように添え字がInt
であればインスタンス変数をNSArray
とみなし、json["name"]
のように添え字がString
であればNSDictionary
とみなして、その要素から新たなJSON
オブジェクトを生成しているわけです。そして要素が存在しない場合は、NSError
からJSON
オブジェクトを生成し、インスタンス変数がNSError
の場合はそのまま自分自身を返すことで、HaskellのEither
が一度Nothing
になればずっとNothing
であるように、最初に発生したエラーが引き継がれるというわけです。
このようなラッパーは同等の機能をフルスクラッチでSwiftで書くよりずっと簡単に書けますし、書くことによってSwiftとObjective-Cの連携がどのようになされているかを体得することもできます。読者の皆さんも、これぞというものがあったらぜひ書いて、GitHubなどで公開してみてください。
AnyObjectとAnyの違い
SwiftのAnyObject
は、Objective-Cにおけるid
に相当します。id
同様なんでも入りますが、適切に使うにはis
で適切な型を判定したり、as
で適切な型に変換したりしなければなりません。
また、Cocoa
やUIKit
など、Objective-C由来のフレームワークをimport
しておく必要もあります(図2)。
ところがSwiftにはAnyObject
とは別にAny
という型も存在します。前述のAnyObject
をAny
に変えてもそのまま動いてしまいますしimport Cocoa
をコメントアウトしてもそのまま動いてしまいます(図3)。
なぜ、Swiftには「なんでもありな型」が2つも存在するのでしょう?
sizeof()
で双方の型を見てみると、面白いことがわかります。64bitプラットフォームではsizeof(AnyObject)
は8なのに対し、sizeof(Any)
は32。AnyObject
は1ワード、Any
は4ワードです。賢明な読者であれば、この時点で予想がつくでしょう。AnyObject
は参照、つまりclass
であるのに対し、Any
は実値、つまりstruct
なのです。
さらに「禁断の組込み関数」、unsafeBitCast
を使ってAny
がどうなっているのかを見てみましょう(リスト7、図4)。
なんのことはない。4ワードのうち頭から本来の値を詰め込んだうえで、最後の1ワードに「型ID」が入っているだけのです。SwiftのStruct
は、Int
やDouble
が1ワード、関数が2ワード、String
やArray
やDictionary
が3ワードなので、Any
の中にすべて納まります。
これに対し、AnyObject
の正体は、Objective-Cで書けばid *
、オブジェクトへのポインタで、型情報はAny
のように値そのものの一部ではなくその参照先に格納されています。
それではAnyはどこで使われているかというと、Xcodeの内部です。Xcodeは現在書かれているコードにあわせて振る舞いを変えますが、この振る舞いを受け取る関数は当然ありとあらゆる型を受け取れなければなりません。Swiftにはreflect()
という関数がありますが、これがAny
を活用している関数の1つで、これを用いると内観(introspect)するためのコードを自作することもできます。
しかしそうでもない限り、Any
を使うケースはほとんどないでしょう。以前紹介したようにSwiftには総称関数とプロトコルがあるので、静的型の特長を活かすためにもAny
の使用は避けるべきです。まとめると次のようになるでしょう。
AnyObject
は、Objective-Cで書かれたフレームワークの連携においてのみ使う
Any
は使わない(複数の型を受け付けるコードには、総称関数とプロトコルを用いる)
続きは次号
今回はSwiftからObjective-Cのフレームワークを用いる例としてSwift-JSONを紹介し、AnyObject
とAny
の違いを垣間見ました。次回はXcodeでCおよびObjective-CのコードとSwiftのコードを同一のプロジェクトで連携する例を見ていくことにします。
- 第1特集
MySQL アプリ開発者の必修5科目
不意なトラブルに困らないためのRDB基礎知識
- 第2特集
「知りたい」「使いたい」「発信したい」をかなえる
OSSソースコードリーディングのススメ
- 特別企画
企業のシステムを支えるOSとエコシステムの全貌
[特別企画]Red Hat Enterprise Linux 9最新ガイド
- 短期連載
今さら聞けないSSH
[前編]リモートログインとコマンドの実行
- 短期連載
MySQLで学ぶ文字コード
[最終回]文字コードのハマりどころTips集
- 短期連載
新生「Ansible」徹底解説
[4]Playbookの実行環境(基礎編)