Update: 続編を書きました
【続編】SwiftUI 導入で LLVM のドキュメント読む羽目になった話
はじめに
この記事は CyberAgent Developers Advent Calendar 2019 9日目の記事になります。
今回は、コンパイラ周りの話も入ってくるので、日頃そこら辺に触れない方でも読んでいただけるようにスライドを多用した記事にしてみました。最後まで読んでいただけると嬉しいです!
SwiftUI 導入してます!
弊社が運用する恋活サービス「タップル誕生」iOS 版では SwiftUI を一部で導入しています。
ただ、導入は簡単ではありませんでした。タイトルの通り、 LLVM のドキュメントを読む羽目になった経緯と問題の解決方法を紹介したいと思います。手っ取り早く 解決方法のみを知りたい! という方は、ページの一番下までスクロールしてみてください!
SwiftUI 導入方法
SwiftUI と UIKit には互換性があるため、共存させることが可能です。なので、 UIKit で設計されたものを SwiftUI のようにプレビューさせることも可能になります。ここらへんに関しては、以前記事を書いてますので参考にしてみてください。
iOS12 以下をサポートするプロダクトで SwiftUI の恩恵を得る方法
参考:AkkeyLab/AutoPreviewable
参考:AkkeyLab/XcodePreviewsTemplate
地獄の始まり
サンプルプロジェクトを作って SwiftUI を試していた私は完全に油断していました。実際のプロダクトで Xcode Preview が動かないんです(※)。しかも、1行の長々とした不親切なエラーを出力するだけ(内容的には、「〜が読み込めませんでした」という感じでした)。
※ SwiftUI を用いた設計・実行等は可能です
先程のエラーの一部で検索を試みますが、 Flutter や Xamarin の記事ばかりで肝心の SwiftUI に関連するのもにはたどり着けませんでした。
この情報不足感は Swift 発表当初を思い出しますね。だからといって、諦めるわけにはいかないのですが、日頃の業務の空き時間等にちょこちょこ SwiftUI への対応を進めていたので、一旦ここで調査を中断しました。
(解釈ミスを修正したスライドに差し替えました)
そして、休日に個人で開発を行っている動画視聴アプリ「AkkeyTV」の SwiftUI 対応を試みました。なんとなくわかっていましたが、同じく Xcode Preview に失敗しました。(スクショは、リポジトリの issue に貼り付けたエラーコード)
ただ、このエラーコードが問題解決に大きく貢献することになります。
先程のエラーをもとに調査を進めたところ、 SwiftUI で同様の問題に直面している記事にたどり着きました。そこに書かれていたことをざっくりとまとめると「カバレッジを有効にした状態でプレビューできない」というものでした。
実際にカバレッジを無効にしてみると tapple でも AkkeyTV でも Xcode Preview が正常に動作するようになりました!しかし、テストを書きながら開発を進めているため、この解決方法はボツにしました。
引用元:Undefined symbols ___llvm_profile_runtime
更に調査を進めたところ、 SwiftUI 以外でも同様のエラーが出ることがあり、それは static framework
に関係しているということがわかりました。そして、 Linker flags に -fprofile-instr-generate
を追加するとうまく動作するという解決策にたどり着いたのです!
実際にフラグを追加してみると tapple でも AkkeyTV でも Xcode Preview が正常に動作するようになりました!
引用元:ReactiveCocoa/ReactiveSwift > issue > Optimization Level configuration #552
状況整理
ここで一旦、状況を整理します。
問題の解決はできたものの、「 -fprofile-instr-generate
というフラグが一体何者なのか」という新たな疑問が生まれてきました。このフラグが原因でアプリが正常に動作しなくなったり、開発効率が低下するようなことがあってはいけないため、このフラグに対する調査も引き続き行っていきます。
問題解決の裏側
まず、「このフラグが何者なのか」この疑問に対する答えがこれです。 clang を使ってソースコードのカバレッジも出力したいときに付加させるフラグだということがわかります。
さて、先程の説明文に出てきた見慣れない用語についても触れておきましょう。
これで、 -fprofile-instr-generate
というフラグに関してはある程度わかったと思います。
ここで、少し前のスライドにフラグに関することも追加してみます。この解釈だと、矛盾が生じているように見えます。
そこで、もう少し視野を広げて、 Xcode のビルド方法について見ていくことにします。
iOS は Objective-C と Swift を1つのプロジェクトで利用できることからもわかるように C 系のコンパイラである clang
と Swift コンパイラである swiftc
によってビルドが行われています。
ここで、 Linker
については こちら を参考にしてください。
内部的に Xcode は各コンパイラに対して引数を渡してコマンドを叩いているだけなので、そのコマンド引数に渡されるオプションについて調べてみることにします。
プロジェクトの設定ファイルなどに記載された各種設定項目から、コマンド引数に渡すオプションに変換するルールブックのようなものが Xcode の奥深くに置かれています。
これは clang
の場合になりますが、 xcspec
という拡張子のファイルに記述されており、カバレッジが有効であるときに2つのフラグをコマンド引数に設定するように記述されていることが確認できます。つまり、カバレッジを有効にした時点で -fprofile-instr-generate
というフラグはコマンド引数に渡されていたということです。
では、 Linker flags に -fprofile-instr-generate
を追加することと、カバレッジを有効にすることにはどのような違いがあるのでしょうか。→【Xcode Build System】
swiftc
に対する xcspec
も同様の処理が記述されています。
同様のファイルは Linker
に対しても存在していますが、カバレッジに関する処理はありませんでした。
ここで、もう少し深く見てみようと思います。
我々が記述するソースコードが機械語に変換されてアプリとして起動するまでを図にまとめてみました。
各種コンパイラと Linker の存在を図に挿入し、渡されるフラグに関しても追記してみました。
ここでわかってくるのは、カバレッジを有効にすることでフラグが渡される対象と、 Linker flags のフラグが渡される対象が異なっているということです。
考察
今後、引き続き調査と勉強を継続していきます。
そのため、この記事には解釈の間違い等が含まれている可能性があります。間違いにお気づきの際は、コメント等いただけると嬉しいです。
さいごに
タイトルに「読む羽目になった」と書きましたが、実際に原因を探り、コンパイラ等の技術について学ぶ時間はとても有意義な時間でした。(記事を読んで伝わったのではないかとは思いますが)嫌々ドキュメントを読んでいたわけではないのでご安心ください!
最後までご覧いただき、ありがとうございました!