Xcode
Swift

Swiftでファイル分割後にエラーしたら疑うところ

More than 3 years have passed since last update.

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を使ってコピーしてから、それぞれのファイルで必要な部分だけ残すという操作になるはずです。図で説明すると、まずコピー。

スクリーンショット 2015-01-31 12.51.46.png

ところがこのダイアログ拡張子が抜けてるので、何も考えずにtest1をtest2に変えてSaveを押すと.swiftが抜けたtest2が出来上がります。

スクリーンショット 2015-01-31 12.51.56.png

となればまぁProject Navigatorからリネームして.swift足しましょう。その後重複部分を消してさあコンパイルとするとこんなエラー。隣のファイルのTest1が見えていません。メッセージはUse of unresolved identifier。普通はtypo等を気にするメッセージだと思います。相変わらず意味不明。

スクリーンショット 2015-01-31 12.52.30.png

ここでさんざん悩むわけですが、途中で気づきました。追加ソースがCompile Sourcesに入っていません。じゃあそもそもコンパイルかからないのでは?という疑念はともかく追加しましょう。

スクリーンショット 2015-01-31 12.52.49.png

追加して、

スクリーンショット 2015-01-31 12.53.02.png

これでよしと思ったらまさかの何も変わらず。

スクリーンショット 2015-01-31 12.52.30.png

根本の問題はこちら。Resourceの方に入っています。この状態だとこのソースはなぜか「コンパイルにかけられるにも関わらず別ファイルの情報は全く見えていない」という謎挙動を示します。ここからtest2.swiftを取り除いたらやっときちんとコンパイルが通るようになりました。

スクリーンショット 2015-01-31 12.53.23.png

さて、お気づきの通り最初のコピーで「.swift」をつけておけば、プロジェクトの設定はこんなことにはならずはじめからコンパイルは通ります。ところがXcodeのUIのせいで問題の操作をしてしまう人はいっぱいいるのではないでしょうか?そして全く原因に気づけない…。

ちなみに私は.swiftをつけてコピーした結果とgit上のdiffを見比べてこの原因を説明できるに至りました。これってつまりプロジェクト管理はやっぱりテキストベースの方が安心だよねって話になるような。もうproject.pbxproj直接いじった方いいような…。


エラーメッセージひどくないですか

一度clang(C++11)のエラーメッセージに慣れるとどうも不親切なメッセージが受け入れがたいです。せめてdo not調ではなくhave to do調で書いてほしい。