C++/Pythonな人がswiftに挑んだら門前払いされた記録です。サンプルソースを見つけてきて、中身が長かったのでクラスごとくらいにファイルを分割しようとしたらコンパイルが通らなくなった時のお話。swiftは__同一Xcode target(moduleと呼ぶ)内にいる別ファイルはimport等書かずに使える__らしいので、本当にファイルを分けるならコンパイル結果が変わらないことを期待するのですが、そんな簡単ではありませんでした。
今のところ私は2つのポイントで騙されました。
1. privateがC++のprivateではないのでエラー
例えば、以下のclassとfuncは同一ソースにあればコンパイル可, 別ファイルに分けるとコンパイル不可です。ちなみにこのときのエラーメッセージが「'Hoge' does not have a member named 'x'」であり、ほぼ意味不明です。C++er以外にはすんなり受け入れられるのでしょうか。
class Hoge{
private var x : Int = 0
}
func someproc(){
var hoge = Hoge()
hoge.x = 5 // !!!!!
}
この場合の理由はprivateというアクセスコントロールで、短いソースならすぐ分かります。するとC++erとしてはむしろなぜファイル分割前はコンパイルできたのか理解できなくなるのですが、swiftのアクセスコントロールはC++とは違って以下のようになるらしいです。
- public: module外からもアクセス可能 (要import文)
- internal: module内のみ (デフォルト, import文不要)
- private: ファイル内のみ
つまり、__C++と違ってクラスのprivateメンバ(プロパティ)が同一ファイル内のクラス外コードから参照できる__のです。逆から見れば、元々単一のswiftファイルをクラスごとに分けたりした場合にprivateメンバが見れなくなってコンパイルエラーという事件が発生します。自分が書いたコードでなかったので気づくのに時間がかかりました。
2. ファイルがCompile SourceではなくBundle Resourceに入ってるからエラー
これはSwift離陸ガイド を一度読み込んだ後でも気づくのに2時間かかりました。ここでは「既存の複数クラスが入ったソースを2つに分割する」という操作を追って説明します。Xcode上なら多分メニューのduplicateを使ってコピーしてから、それぞれのファイルで必要な部分だけ残すという操作になるはずです。図で説明すると、まずコピー。
ところがこのダイアログ拡張子が抜けてるので、何も考えずにtest1をtest2に変えてSaveを押すと.swiftが抜けたtest2が出来上がります。
となればまぁProject Navigatorからリネームして.swift足しましょう。その後重複部分を消してさあコンパイルとするとこんなエラー。隣のファイルのTest1が見えていません。メッセージはUse of unresolved identifier。普通はtypo等を気にするメッセージだと思います。相変わらず意味不明。
ここでさんざん悩むわけですが、途中で気づきました。追加ソースがCompile Sourcesに入っていません。じゃあそもそもコンパイルかからないのでは?という疑念はともかく追加しましょう。
追加して、
これでよしと思ったらまさかの何も変わらず。
根本の問題はこちら。Resourceの方に入っています。この状態だとこのソースはなぜか「コンパイルにかけられるにも関わらず別ファイルの情報は全く見えていない」という謎挙動を示します。ここからtest2.swiftを取り除いたらやっときちんとコンパイルが通るようになりました。
さて、お気づきの通り最初のコピーで「.swift」をつけておけば、プロジェクトの設定はこんなことにはならずはじめからコンパイルは通ります。ところがXcodeのUIのせいで問題の操作をしてしまう人はいっぱいいるのではないでしょうか?そして全く原因に気づけない…。
ちなみに私は.swiftをつけてコピーした結果とgit上のdiffを見比べてこの原因を説明できるに至りました。これってつまりプロジェクト管理はやっぱりテキストベースの方が安心だよねって話になるような。もうproject.pbxproj直接いじった方いいような…。
エラーメッセージひどくないですか
一度clang(C++11)のエラーメッセージに慣れるとどうも不親切なメッセージが受け入れがたいです。せめてdo not調ではなくhave to do調で書いてほしい。