2015年12月17日木曜日

レガシーな Objective-C プロジェクトを Swift なプロジェクトに変換する

ここで言うレガシーなObjective-Cプロジェクトの定義とは
  • iOS 7時代 (Xcode 5) より前に作成されたプロジェクトである
  • Swiftのコードを一行も含んでいない
  • IOS_DEPLOYMENT_TARGETが8.0よりも小さい (7.xをサポートしている)
とします。

こんな由緒正しいiOSのプロジェクトを未だにメンテしている人もなかなかいないのかと思いますが、もしいらっしゃいましたらそんな方のためにSwiftなプロジェクトに変換していく方法をメモしておきます。

■前提条件

まずIOS_DEPLOYMENT_TARGETを8.0以上にしましょう。IOS_DEPLOYMENT_TARGETを8.0以上にすることでdynamic frameworkおよびclang moduleが使えるようになるため、Objective-CとSwiftの間の垣根が非常に低くなります。

■ケース1: 根っこはObjective-Cのまま、Swiftのファイルを追加

普通にSwiftのファイルを追加したらXcodeが上手いことしてくれます。具体的には

clang moduleを有効にしてdyldを使えるようにする設定
CLANG_ENABLE_MODULES = YES;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";

Swift化されるターゲットごとにブリッジングヘッダを作成
SWIFT_OBJC_BRIDGING_HEADER = "myapp/myapp-Bridging-Header.h";

Releaseビルド以外に対して最適化レベル設定
SWIFT_OPTIMIZATION_LEVEL = "-Onone";

が自動的に行われるので後はせいぜいブリッジングヘッダに使ってるObjective-Cのヘッダを書き込む程度ですみます。

■ケース2: 根っこからSwiftにする

「根っこからSwift」とは要するにmain.m(C言語のmain関数)を持たないアプリにしたいということですが、これも実は思ったより簡単にできます。
  • AppDelegate.swiftファイルを作成する
  • 既存のObjective-Cで書かれたAppDelegateを継承て新しくswiftなAppDelegateを作成して、@UIApplicationMainアノテーションを付ける
  • main.mを消す
参考: http://stackoverflow.com/questions/31309249/how-to-convert-objective-c-appdelegate-to-swift

たったのこれだけでプロジェクトの根っこがSwiftな状態になります。簡単ですね。

■さらにSwift化をすすめる

とりあえずAppDelegate.swiftを作ることで根っこはSwiftになるのですが、せっかくだからAppDelegateをまるごとSwiftにしてしまいたいものです。ここでもし、
  • UIWindowをAppDelegate内部でカスタマイズしている
  • UIApplicationをmain.mでカスタマイズしている
などの場合はそのままだとSwiftで対応するのがちょっと面倒です。

まずUIApplicationについてはInfo.plistのNSPrincipalClassを変更することで任意のカスタムクラスに差し替える事が可能です。参考はこちら: http://stackoverflow.com/questions/31642956/how-to-detect-all-touches-in-swift-2

UIWindowについてはMain.storyboardを使わなければ勝手にUIWindowを差し込まれることはなくなるので問題ないのですが、それでは困ると言う場合はawakeFromNibとかを実装してその中で
@UIApplicationMain
class MyAppDelegate: NSObject, UIResponder, UIApplicationDelegate {
  var window: UIWindow?
  override func awakeFromNib() {
    super.awakeFromNib()
    guard let defaultWindow = self.window else {
      fatalError("Something is wrong")
    }
    let window = MySuperDuperWindow()
    window.rootViewController = defaultWindow.rootViewController
    self.window = window
  }
}
とかすれば動くと思います。多分。