swiftcを使ったUIKitコードのコンパイル: フレームワーク問題の解決方法
Swiftのプログラムをコンパイルする際には、swiftcというコマンドラインツールがよく利用されます。swiftcは非常に強力なツールであり、多様なコンパイルオプションや診断機能を提供しています。しかし、UIKitのように特定のフレームワークを利用する場合には、特別な設定が必要になることがあります。
問題の背景
SwiftUIを使用した場合、大抵は問題なくコンパイルできます。しかし、UIKitのコードをコンパイルするときにエラーが発生することがあります。
以下はmain.swiftとして実装されたシンプルなUIViewControllerのサンプルコードです。
import UIKit
final class ViewController: UIViewController {
private var isShow = false
override func viewDidLoad() {
super.viewDidLoad()
}
private func toggleFunction() {
isShow.toggle()
}
private static func staticFunction() {
// do something
}
}
このコードはiOSアプリケーションの一部として非常にありそうなものです。しかし、このコードを単体でswiftcを使ってターミナル上でコンパイルしようとすると、次のようなエラーが発生します。
コンパイルを行うコマンド
$ swiftc main.swift -emit-sil -o sil.swift
発生するエラー
error: no such module 'UIKit'
問題の解決方法
この問題の根本的な原因は、swiftcがデフォルトではmacOSのためのコンパイルを行っているため、UIKitを認識できないからです。UIKitのフレームワークを認識させるには、-targetや-sdk、そして-Fといったフラグを使用して、適切なターゲット指定とSDKのパスをコマンドに追加する必要があります。
具体的には、次のコマンドを実行することでエラーを解消することができます。
swiftc -target arm64-apple-ios17.0 -sdk $(xcrun --sdk iphoneos --show-sdk-path) -F $(xcrun --sdk iphoneos --show-sdk-platform-path)/Developer/Library/Frameworks main.swift -emit-sil -o sil.swift
コマンドの詳細
-
-target arm64-apple-ios17.0- コンパイルのターゲットをiOSの
arm64アーキテクチャに設定します。17.0の部分はiOSのバージョンに応じて変更可能です。
- コンパイルのターゲットをiOSの
-
-sdk $(xcrun --sdk iphoneos --show-sdk-path)- 使用するSDKのパスを指定します。この部分で実際のiOS SDKのパスが動的に取得されます。
-
-F $(xcrun --sdk iphoneos --show-sdk-platform-path)/Developer/Library/Frameworks- iOSのフレームワークのパスを指定します。これにより、
swiftcはUIKitのようなiOS専用のフレームワークを正しく認識できます。
- iOSのフレームワークのパスを指定します。これにより、
-
-emit-sil -o sil.swift-
silを出力し、結果をsil.swiftファイルに保存します。silを出力する必要がない場合は削除しても問題ありません。
-
まとめ
Swiftのコマンドラインツールswiftcを使用し、UIKitを含んだコードをコンパイルする際には、ターゲットやSDK、フレームワークのパスを適切に設定することが必要です。上記のコマンドを使用することで、この問題を解決することができます。
開発環境
- Swift compiler version info:
Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1)
- Xcode version info:
Xcode 15.0 Build version 15A240d (Release Candidate)
余談
上記のコマンドで生成されたsil.swiftを見ると、UIViewControllerが暗黙的にMainActorであることがわかります。以下にコードの一部を示します。
@objc @_inheritsConvenienceInitializers @MainActor final class ViewController : UIViewController {
@_hasStorage @_hasInitialValue @MainActor private final var isShow: Bool { get set }
@MainActor override final func viewDidLoad()
@MainActor private final func toggleFunction()
@MainActor private static func staticFunction()
@MainActor override dynamic init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
@MainActor required dynamic init?(coder: NSCoder)
@objc deinit
}
Discussion
SIL: Swift Intermediate Language (SIL)
【iOS】Xcodeのビルドの仕組みを知り、ビルド時間を短縮する方法を探る - Qiita