✈️

swiftcを使ったUIKitコードのコンパイル: フレームワーク問題の解決方法

2023/09/20に公開1

Swiftのプログラムをコンパイルする際には、swiftcというコマンドラインツールがよく利用されます。swiftcは非常に強力なツールであり、多様なコンパイルオプションや診断機能を提供しています。しかし、UIKitのように特定のフレームワークを利用する場合には、特別な設定が必要になることがあります。

問題の背景

SwiftUIを使用した場合、大抵は問題なくコンパイルできます。しかし、UIKitのコードをコンパイルするときにエラーが発生することがあります。

以下はmain.swiftとして実装されたシンプルなUIViewControllerのサンプルコードです。

main.swift
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のバージョンに応じて変更可能です。
  • -sdk $(xcrun --sdk iphoneos --show-sdk-path)
    • 使用するSDKのパスを指定します。この部分で実際のiOS SDKのパスが動的に取得されます。
  • -F $(xcrun --sdk iphoneos --show-sdk-platform-path)/Developer/Library/Frameworks
    • iOSのフレームワークのパスを指定します。これにより、swiftcUIKitのような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であることがわかります。以下にコードの一部を示します。

sil.swift
@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
}