M1 Mac の Xcode 12.4 で、別 Framework に分けたカスタム View の @IBDesignable が効かない件。

追記 2021/6/8 17:10

Xcode 12.5 では直ってなかったが Xcode 13 Beta で直ったようだ。めでたい。

追記 2021/2/6 0:30

Apple にバグレポート送った。

概要

  • Intel Mac だと再現しない。M1 Mac のみ。

  • たぶん Xcode 側に問題があるので、待ってりゃ直るんじゃないかな。

    • いやでも望みは薄いかもしれない…。
      • InterfaceBuilder と @IBDesignable 関連について Apple 的には失敗作だったと思ってそうな節があるような気がしている…(個人の感想です)
        • 今後一切メンテナンスされない可能性もあるかもしれない…(個人の感想です)
  • 手元でちょっとプレビューさせるくらいなら力技で一応なんとかできる。

    • しかし他の部分が色々と壊れるので微妙。
      • 全体的につらい話。

詳細

世間的な流れはもう SwiftUI なんだろうけど、実際のところ全てを SwiftUI で実装していけるかというと別にそんなことはない。ややこしいインタラクションを実装したりするなら UIView をベースに作るのは今でも良い選択だと思う。

とは言え、個人的にはコードでの UIView 関連の実装を避けられるなら出来るだけ避けたい。そういう訳で、自分が面倒を見ている範囲では @IBDesignable@IBInspectable を多用している。

@IBDesignable を便利に使うテクニックとして、 TARGET を分けて別の framework にしておく手法がよく知られている。こうすることによって、 InterfaceBuilder を開いたときに走るビルドを最小限に抑えられるというメリットがある。

要するにこんな感じ

要するにこういう図の感じに設定して、この例だと DesignableComponents っていう Framework を切っているのだが、その Framework の中でカスタムの DesignableView みたいなのを実装しつつ、アプリケーション側の TARGET にある Storyboard とか xib でそれを使う、というイメージ。

その中で @IBDesignable 周りと InterfaceBuilder 周りはあまりメンテされてなさそうな感じというか、そういうやる気がなさそうというか、メンテが難しそうな構造になってるのかなというか、各所に微妙なハードコーディングされた実装がありそうだなというか、そういう雰囲気を感じさせるエラーが発生しているという、つらい話です。

とりあえず最小限で再現する xcodeproj を作った。

https://github.com/dnpp73/BrokenFrameworkDesignable

問題が起こるのは M1 搭載機種のみ。これを Xcode 12.4 で開いて Main.storyboard を見ると

無慈悲なエラー

無慈悲なエラー

こんなエラーが出る。

エラーメッセージが言ってることは大体同じで、要するに DerivedData 内にあるべき framework が見付からんぞ、見付かったとしても iOS Simulator 向けのビルドじゃないから使えんぞ、といった感じである。

どうやら M1 と Intel で InterfaceBuilder が framework を探しにいく先が違っている。

見出しの通りなんだけど、どうやら

  • M1 Mac : ${Configuration}-iphoneos ディレクトリ内を探しにいく。
  • Intel Mac : ${Configuration}-iphonesimulator ディレクトリ内を探しにいく。

という感じのようだ。この場合 Intel Mac 側の挙動が正しい。恐らく Xcode の実装のどこかにアーキテクチャ依存の分岐か何かがハードコーディングされてるんじゃないか…と思わせる挙動である。

ちなみに DerivedData 内の [...]/Build/Products 内に

  • ${Configuration} → Mac 向けの成果物
  • ${Configuration}-iphoneos → iPhone 実機向けの成果物
  • ${Configuration}-iphonesimulator → iOS Simulator 向けの成果物

が入ることには変わりないし、ここは間違ってない。

困ったことに M1 Mac だと ${Configuration}-iphoneos を探しに行ってしまい、そこには普通にしていると Simulator 向けのビルドが入ることがない (実機向けのビルドが入ることはある) ため、エラーメッセージがちょいちょい変わりながら出続けてユーザーを翻弄してくることになる。つらい。

そして実際の開発現場ではもっと複雑な xcodeproj を運用しているだろう。入り組んだ Framework Target やら、そこに Carthage や Swift Package Manager やらで依存ライブラリを入れたりなんだりしてるはずで、ビルド時のエラーメッセージがちょいちょい分かり辛い感じになる。実際はもっとつらい。

とりあえず力技で解決する。

とりあえず探しに行った先に必要なファイルが存在さえすれば @IBDesignable のプレビューだけは通るようになる。

この場合は

  1. Simulator 向けにビルドする。
  2. DerivedData 内に生成された ${Configuration}-iphonesimulator${Configuration}-iphoneoscp -r する
  3. InterfaceBuilder 上で、メニューから EditorRefresh All Views するか Xcode を再起動する。

という泥臭いことをすれば、一応通る。

しかしこんな野蛮なことをするのは全くよくない。

本来実機向けビルド成果物が入るところにシミュレータ向けのビルド成果物を入れてる訳なので、このまま DerivedData を掃除せずに実機で実行させようとすると死ぬと思われる。

ちゃんと解決する (やってない)

ちゃんと対応するのは結構めんどい感じがしている。

というか Apple が見限ろうとしているものにしがみ付くのは良くないことは経験的に分かっていて何とも言えない。SwiftUI へ移行せよということか。まだちょっと色々不便なんだけどな…。

ちゃんとやるなら Xcode にバグレポートを送るべきだろうし (追記: 送りました) 、直るまでの間はワークアラウンドなシェルスクリプトを書いて、適当な位置に Run Script フェーズを挟んでそこ実行するみたいなことをやることになるか、そんな気がしている。

ググってる最中で 同じ問題に直面して困っている人を Stack Overflow でも見付けた のだが、ここに付いてるレスでは解決できないように思われる。世知辛いですね。(追記: 一応解決方法あるけど微妙だよねみたいなコメント書いときました)

締め

M1 の MacBook Air をメインの開発機にしてから数週間経った。

細かい問題は度々発生するものの、都度ググれば大体先人が何かしら工夫して解決している情報に辿り着けるし、そうでなくてもまぁ大体こんなもんだろって感じで解決策にアタリを付けて回避はいくらでもできるし、その辺はあまりデメリットにならないと感じている。

iOS アプリ開発の現場では…問題は結構起こるけど、まぁなんとかなる。
体感としては大体アーキテクチャ依存の問題を引いてくる感じ。

というか Web フロントエンド開発や Rails 周りの開発を、ローカル環境だったり手元の Docker 環境に作ってなんやかんや仕事する方が問題が起こらないまである。

色々差し引いてもワットパフォーマンスが異常に良くてバッテリが死ぬほど持つし、全然熱くならないし、普通に速いし、キーボードは打ちやすい…。

Touch Bar 搭載のモバイル向け Intel プロセッサ搭載 MacBook Pro は全体的に悪夢だったと言える。あれは一体なんだったんだ。

Recents
2021/2/4 22:38
2019/10/26 18:12