【Xcode】細分化する iOS Architecture に向き合う上で気をつけなければらないただひとつのエラーについて

## はじめに

こんにちは。Reactive な世界に生命の息吹を感じるたかねです。

今年もやってきました，[iOS Advent Calendar 2019](https://qiita.com/advent-calendar/2019/ios) 1日目です!
みなさまよろしくお願いいたします!

本記事は `unable to spawn process (Argument list too long)` という Xcode からのすてきな (!) メッセージについてです。
普段の開発では見慣れないエラーかと存じます。しかし，本問題を知り，本問題を見据えて開発することは，きっと数年後の iOS 開発者であるあなたの役に立つと思います。少し長いですが，ぜひご覧いただけましたら幸いです。

## 目次

- [最近のソフトウェアアーキテクチャに関する流行と振り返り](https://qiita.com/YutoMizutani/items/ae6282dee4d14b59996e#%E6%9C%80%E8%BF%91%E3%81%AE%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E6%B5%81%E8%A1%8C%E3%81%A8%E6%8C%AF%E3%82%8A%E8%BF%94%E3%82%8A)
- [プロジェクトが適切に肥大化し続ける中待ち受ける，たったひとつのエラー](https://qiita.com/YutoMizutani/items/ae6282dee4d14b59996e#%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%8C%E9%81%A9%E5%88%87%E3%81%AB%E8%82%A5%E5%A4%A7%E5%8C%96%E3%81%97%E7%B6%9A%E3%81%91%E3%82%8B%E4%B8%AD%E5%BE%85%E3%81%A1%E5%8F%97%E3%81%91%E3%82%8B%E3%81%9F%E3%81%A3%E3%81%9F%E3%81%B2%E3%81%A8%E3%81%A4%E3%81%AE%E3%82%A8%E3%83%A9%E3%83%BC)
- [`unable to spawn process (Argument list too long)` とは? その発生条件](https://qiita.com/YutoMizutani/items/ae6282dee4d14b59996e#unable-to-spawn-process-argument-list-too-long-%E3%81%A8%E3%81%AF-%E3%81%9D%E3%81%AE%E7%99%BA%E7%94%9F%E6%9D%A1%E4%BB%B6)
- [いくつかの対処方法と，Xcode 11 における解決方法](https://qiita.com/YutoMizutani/items/ae6282dee4d14b59996e#%E3%81%84%E3%81%8F%E3%81%A4%E3%81%8B%E3%81%AE%E5%AF%BE%E5%87%A6%E6%96%B9%E6%B3%95%E3%81%A8xcode-11-%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E8%A7%A3%E6%B1%BA%E6%96%B9%E6%B3%95)
- [まとめ](https://qiita.com/YutoMizutani/items/ae6282dee4d14b59996e#%E3%81%BE%E3%81%A8%E3%82%81)
- [(おまけ) なぜ日本語記事が存在しなかったのか](https://qiita.com/YutoMizutani/items/ae6282dee4d14b59996e#%E3%81%8A%E3%81%BE%E3%81%91-%E3%81%AA%E3%81%9C%E6%97%A5%E6%9C%AC%E8%AA%9E%E8%A8%98%E4%BA%8B%E3%81%8C%E5%AD%98%E5%9C%A8%E3%81%97%E3%81%AA%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%AE%E3%81%8B)
- [注釈](https://qiita.com/YutoMizutani/items/ae6282dee4d14b59996e#%E6%B3%A8%E9%87%88)
- [文献](https://qiita.com/YutoMizutani/items/ae6282dee4d14b59996e#%E6%96%87%E7%8C%AE)

## 最近のソフトウェアアーキテクチャに関する流行と振り返り

本題に入る前に少しおさらい、もとい振り返りをさせてください。2018年は [iOSアプリ設計パターン入門](https://peaks.cc/books/iOS_architecture) を始め，[Clean Architecture: A Craftsman's Guide to Software Structure and Design](https://www.amazon.co.jp/dp/0134494164/ref=cm_sw_r_tw_dp_U_x_64N4Db2DXAHF3) の訳書である [Clean Architecture 達人に学ぶソフトウェアの構造と設計](https://www.amazon.co.jp/dp/4048930656/ref=cm_sw_r_tw_dp_U_x_bYN4DbDQMSR5D) が出版され，日本の iOS 開発者が様々なソフトウェアアーキテクチャに対し知見を得る1年となりました。実際に MVVM を始め、多くの企業は細分化されたアーキテクチャを採用している様子が確認できるようになってきています。我々 iOS エンジニアは設計というスキルを得ることができ，次なる問題に取りかかることができるようになりました。

## プロジェクトが適切に肥大化し続ける中待ち受ける，たったひとつのエラー

閑話休題。ここでの「プロジェクト」とは Xcode の Project と同義であり，ソースコードとその周辺環境を指します。
過去に [FATViewController](https://dev.classmethod.jp/smartphone/iosdc-2017-setsuko/) などと揶揄されたこともあった `UIViewController` へのドメインロジック混入による肥大化は，View と Model を分離 ([プレゼンテーションとドメインの分離](http://bliki-ja.github.io/PresentationDomainSeparation/); Presentation Domain Separation) することを目的とした各種設計により解決されました。

素晴しく綺麗な環境で開発される iOS プロジェクトは，適切に分割されたレイヤーによりテストがし易く堅牢で，かつ明快である。その環境下では無限にスケールしてもその効用を維持し続けることができる，その最たる例が Clean Architecture である。

そう，私もそう信じてやみませんでした。件のエラーが発生するまでは。

以下をご覧ください。これは，ある条件下において Xcode でビルドした際に発生するエラーです。

`unable to spawn process (Argument list too long)` エラーです。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195913/70d3978b-d7cf-1587-29f3-49fd23ad72f3.png">

これが Xcode である証拠に，[画面全体のスクリーンショット](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195913/79177b3c-4fc6-8a42-4beb-abfb170c65eb.png) も添付しておきます。

また，`xcodebuild` コマンドによる CLI 経由でも発生します。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195913/29c274e4-d6cf-634a-662c-46d3c15358b2.png">

これは一体何でしょうか? この記事をご覧になっている皆様にも発生する問題なのでしょうか?

## `unable to spawn process (Argument list too long)` とは? その発生条件

`unable to spawn process (Argument list too long)` エラーが発生する原因はただひとつ。エラー文の通り「**引数リストが多過ぎる**」というエラーです。


*Compile Swift source files* ステップにて， **コンパイラに渡されるソースファイルの Full path とオプションの文字数の合計がおよそ 260,000 文字程度※1 を越えるとコンパイルが必ず失敗する** というものです。


............遭遇しなければ気がつかないエラーです😇😇😇😇😇


## いくつかの対処方法と，Xcode 11 における解決方法

ファイル数が多い，絶対パスが長くなるような深いディレクトリに存在する，極端に長いファイル名を使用しているなどの原因により本問題は発生します。

その解決方法は `swiftc` (Swift コンパイラ) に対し渡すコマンドの総量をどうにかして減らすことのみでした。今秋リリースされた Xcode 11  (正確には [Xcode 11 beta 3](https://gist.github.com/earltedly/f434e479c05852d6749c4de87d50c080#new-features-in-beta-3))ではようやく解決可能なフラグが追加されました。
いくつかの対処方法と，Xcode 11 における解決方法を順に解説いたします。

#### 1. ライブラリをビルド済みバイナリ形式でビルドする

こちらは [CocoaPods](https://github.com/CocoaPods/CocoaPods) を利用している場合に限ります。[Carthage](https://github.com/Carthage/Carthage) はビルド済みバイナリを利用しますが，CocoaPods の場合ビルドが含まれるため，その分パスが長くなります。どうしても CocoaPods を利用したい方は，[cocoapods-binary](https://github.com/leavez/cocoapods-binary) というプラグインを導入することで，インストール時にバイナリとして事前にコンパイルさせておくことが可能です。

余談ですが，[cocoapods-binary](https://github.com/leavez/cocoapods-binary) はオプションによりソースコードを残しつつバイナリを生成することもできます。本オプションにより，Carthage の弱点であった「開発時に Xcode 内からライブラリ側のソースコードを辿れない」という問題に対応しつつ，ビルド時にはコンパイル済みのバイナリを利用してコンパイルを早くするといったことが可能となります。便利です。

#### 2. スクリプトによってコンパイル対象のソースファイル名を変更する

愚直な解決方法として挙げられるのはこちらです。「ファイル名の長さが原因」なので，実際にコンパイラに渡すまでに短いファイル名に変更してからコンパイルするようにすれば良い，ということになります。
具体的な方法はあまりにも泥臭いため省略します。

#### 3. プロジェクトをルートディレクトリに移動させる

コンパイラに渡されるソースファイルのパスは *Full Path* であるという仕様を利用する方法です。リポジトリを Clone するディレクトリをルート直下に配置するだけです。全てのソースファイルに影響があるためファイル数が多いプロジェクトほど効果があります。ちなみに Apple の [Developer forum](https://forums.developer.apple.com/thread/86762) にも同様の提案がされています。
一方で，これは一時的解決に過ぎず，この状態で開発を続けてもいずれエラーとなることは目に見えています。
(Bitrise がルートディレクトリに Clone する仕様で助かりましたね!)

#### 4. [Embeded Framework](https://developer.apple.com/library/archive/technotes/tn2435/_index.html) を利用してコンパイルする

[Embeded Framework](https://developer.apple.com/library/archive/technotes/tn2435/_index.html) を用い，分割コンパイルすることで一度にコンパイルするソースファイルの数を減らす方法です。これが一番まともであり，根本解決とは異なりますが正攻法と言えるでしょう。

#### 5. Xcode 11 を利用してビルドする ~~`USE_SWIFT_RESPONSE_FILE` を `YES` と指定する (Xcode 11 以降)~~

~~[Xcode 11 以上の環境で build settings `USE_SWIFT_RESPONSE_FILE` に `YES` を代入する](https://stackoverflow.com/a/58762062) ことで解決できるとの情報が更新されていました。~~Xcode 10 まででは根本解決に至らなかったこのエラーは，Xcode 11 によってようやく解決されたようです。

**訂正 (2019/12/04)**: `USE_SWIFT_RESPONSE_FILE` は Xcode 11 Release 時点でデフォルトが `YES` となりました。 よって，Xcode 11 を用いてビルドすることで本問題は解決します。

https://gist.github.com/earltedly/f434e479c05852d6749c4de87d50c080#new-features-in-beta-3
> You can use an unlimited number of Swift files in a target if you set the build setting USE_SWIFT_RESPONSE_FILE to YES. (35879960)

https://developer.apple.com/documentation/xcode_release_notes/xcode_11_release_notes
> Xcode uses response files by default to pass input files to the Swift compiler. To turn this behavior off, set USE_SWIFT_RESPONSE_FILE to NO. (50852028)

## まとめ

- Xcode には コンパイラへ渡すソースファイルの内容によって `unable to spawn process (Argument list too long)` が発生する致命的な問題を潜在的に抱えていた
- Xcode 11 未満は Embeded Framework による Target 分割を始めとした回避方法が存在する
- Xcode 11 以降であれば ~~`USE_SWIFT_RESPONSE_FILE` に `YES` を代入することで~~ 解決できる

秋に発表された Xcode 11 は図らずとも我々に良い結果をもたらしました。
一方で堅牢な設計を維持するためには Embeded Framework を利用することがコンパイル時間の短縮のためにも良いかと思います。投稿が少し遅くなりましたが，ここらで筆を置かせていただきます。

## (おまけ) なぜ日本語記事が存在しなかったのか

本章は考察です。Xcode には以前よりこのような問題を抱えていました。[少なくとも 2017 年には](https://forums.developer.apple.com/thread/86762)。
私が iOS 開発を始めたのも 2017 年ですので、それ以前の流れは分かりません。設計に関する関心は恐らくこれと同じ時期、もしくはそれ以後に盛況を呈したものと思われます。

#### 2017 年以後，設計に関心を持つ開発者が本問題に遭遇することはなかったのか？

この疑問については，周囲から話を聞く限りでは「細分化される Embeded Framework を利用しており，発生する前に自然と回避していた」と考えられます。

#### Swift における名前空間について

他言語ではディレクトリ名が名前空間として予約され，異なるディレクトリ間でも問題なく扱える機能がありますよね。
Swift における名前空間は暗黙的であり，利用するためには Framework による Target の分離が必要です。Framework によって分割するということはコンパイルソースを分割することにもなるため，名前空間による解決手法は結果的に上で記載した手法に含まれます。
(この仕様自体も，ソースファイルの命名が長くなる要因だったり......?)

#### エラー原因の背景1 – XcodeGen の導入とリファクタリングの順序 – 

今回エラーとなった原因の背景には「MVC から Clean Architecture へのリファクタリング → [XcodeGen](https://github.com/yonaskolb/XcodeGen) によるコンフリクトの解消 → [Embeded Framework](https://developer.apple.com/library/archive/technotes/tn2435/_index.html) による分割」 のステップを検討しており，同時に Framework 化を進められなかった事情がありました (こんなエラーが発生することを予期できていれば最初から Target を分けて作成していましたよ!)。その結果，1 target に全ての層が入りこみ，MVC から分割されたファイルは必然的に増える結果を生みました。

#### エラー原因の背景2 – Presentation 層への Atomic design を用いた介入 – 

さらに，今日までのソフトウェアアーキテクチャにおける欠点のひとつに「View (Presentation) の実装については関心を持たない」という点も加筆しておかねばなりません。昨年 [Sketchと1対1を目指すAtomic designなStoryboardの作り方 - Qiita](https://qiita.com/YutoMizutani/items/e14c918232af83f34e29) という記事を書きました。Presentation 層に対し無関心であった部分について，[Atomic design](http://atomicdesign.bradfrost.com/table-of-contents/) を利用して再利用可能なレベルに分割する手法を導入しました。こちらも，再利用を意識し過剰な `.xib` ファイルとその対となる `.swift` が生成され，ファイル数の増加に繋がりました。

#### Presentation 層におけるかすかな希望

[Atomic design](http://atomicdesign.bradfrost.com/table-of-contents/) 自体が悪かった訳ではありません。実際に再利用によってボタンやフォームを始めとする UI の作成速度は向上し，意図せぬ再実装がなくなりました。また，[SwiftUI](https://developer.apple.com/jp/xcode/swiftui/) の登場は Storyboard および Xib ファイルからの開放，コード生成と GUI による生成の共存という結果を生み出しました。これだけでも旧実装よりファイル数，コード数ともに削減させる一手と成り得る希望を生みました (完全に Replace できるかについて，ここでは言及を避けるものとします)。

## 注釈

※1) 上限の境界については未検証のため正確ではありません。263,627 文字で失敗を確認しています。

## 文献

- [xcodebuild exportLocalization throws "Argument list too long"
](https://forums.developer.apple.com/thread/86762)
- [error: unable to spawn process (Argument list too long) in Xcode Build](https://stackoverflow.com/questions/53226087/error-unable-to-spawn-process-argument-list-too-long-in-xcode-build/53229164)
- [iOSDC 2017 前夜祭で「節子、それViewControllerやない…、FatViewControllerや…。」というタイトルで登壇しました！ #iosdc ｜ Developers.IO](https://dev.classmethod.jp/smartphone/iosdc-2017-setsuko/)
- [CocoaPods/CocoaPods - GitHub](https://github.com/CocoaPods/CocoaPods)
- [leavez/cocoapods-binary - GitHub](https://github.com/leavez/cocoapods-binary)
- [Carthage/Carthage - GitHub](https://github.com/Carthage/Carthage)
- [Swiftで名前空間を利用する - Qiita](https://qiita.com/yimajo/items/56c51228ca29c817f3c1)
- [プレゼンテーションとドメインの分離 - Martin Fowler's Bliki (ja)](http://bliki-ja.github.io/PresentationDomainSeparation/)
- [yonaskolb/XcodeGen - GitHub](https://github.com/yonaskolb/XcodeGen)
- [Embedding Frameworks In An App - Apple Developer](https://developer.apple.com/library/archive/technotes/tn2435/_index.html)
- [Sketchと1対1を目指すAtomic designなStoryboardの作り方 - Qiita](https://qiita.com/YutoMizutani/items/e14c918232af83f34e29)
- [Atomic design](http://atomicdesign.bradfrost.com/table-of-contents/)
- [SwiftUI - Apple Developer](https://developer.apple.com/jp/xcode/swiftui/)
- [Swift Compilation - Reaching ARG_MAX limit causing Xcode build failure - Swift Forums](https://forums.swift.org/t/swift-compilation-reaching-arg-max-limit-causing-xcode-build-failure/6494)
- [earltedly/Xcode beta 2 -> beta 3 changes.markdown -  GitHub Gist](https://gist.github.com/earltedly/f434e479c05852d6749c4de87d50c080#new-features-in-beta-3)
- [Xcode 11 Release Notes | Apple Developer Documentation](https://developer.apple.com/documentation/xcode_release_notes/xcode_11_release_notes)