Swift

Objective-CのコードをSwiftに書き換える時の流れメモ

More than 3 years have passed since last update.

内容

個人でObjective-Cで書きかけていたiPhoneアプリのコードをSwiftに書き直してみたので、その時にどういう流れでやったかとあと注意点をメモしておきます。

前提

勉強とかの理由がなければ Objective-C を Swift に書きかえるのはおそらくあまり意味がないです。

Objective-C は今後も使われる(ふつうのC言語をばりばり使ってるコードとかたぶん Swift では書けないし)ので、秋以降にリリースする新規アプリでいちから Swift で書くのが Apple 的にも正解なんだろうなーと思います。

これを書いてる人は

Objective-C で仕事している人ではないので Objective-C 自体そんなに書いてる訳ではないです。
環境は OSX 10.9 で WWDC 後に出た Xcode6-beta 使ってます。iOSは8じゃなくて7でやって、iPhone 5s で実機で動かしました。

流れ

1. 書き換えるクラスを選ぶ

  • Swift のクラスを継承した Objective-C のクラスは作れないのでベースクラス的なのじゃなくて末端のクラスを書き換える。
  • Cのコードがたくさん入ってたらあきらめる。あと Underscore.m のコードを書き換えるのは無理そうでした。(ちなみに Swift の map とかで書き換えようとしたら静的チェックのエラー?が出て解決できなかったので結局ただの for 文にしました…)

2. クリーンしておく

  • まずシミュレータやデバイスについてビルドが必要なものはそれぞれクリーンしておく
  • クラスを1つだけ書き換えたりすると依存関係でビルドエラーが出るようになったりするのでクリーンをしておいた方がいいです。
    • それでもビルドエラーで Dependency Analysis Error みたいのがでるようになったら Xcode をいったん終了して、DerivedData とかの中間ファイルを色々消せば多分なんとかなります

3. importの書き換え

  • 書き換えるクラスで import してるヘッダファイルについて xxx-Bridging-Header.h (最初に Swift のソースを追加する時に一緒に追加されるファイル) と xxx-Prefix.pch の両方で import しておく
  • 元の Objective-C のヘッダを import してるところは xxx-Prefix.pch や xxx-Bridging-Header.h から削除
  • あともちろん元々 .pch に書いていて書き換えるクラスに必要な import は xxx-Bridging-Header.h にも書く

4. 既存の Objective-C のクラスをどうにかする

  • 多分消してもいいと思いますが、戻したくなった時のために自分はいったんクラス名を変えました(ファイル名と .h .m それぞれの中で検索して置換)
    • あと自分は Swift に書き換え完了後、プロジェクトからObjective-Cファイルの参照だけ削除するようにしましたがその辺は適当に

5. Swiftの新規クラスを追加してビルドが通るまでひたすら書き換える

  • シングルトンはどれがベストなのかよく分からないですが dispatch_once singleton model in swift を参考におなじみの dispatch_onct_t かあるいはグローバル変数使う感じで。このリンク先にも載ってるように Apple のサンプルは struct でやってました
  • Objective-C から使いたいクラスとプロパティには @objc いれとく
  • [[HogeHoge alloc] init] できるようにするには以下をいれておく
    @required init() {
    }
  • UIViewController とか UITableViewCell とかはとりあえず init(coder:) を追加しておく
    • というかいざ表示しようとするとエラーが出ることがあるので、その時に追加でもいいと思います
    init(coder aDecoder: NSCoder!) {
        super.init(coder: aDecoder)
    }
  • 色んなオプション指定(NS_ENUM、NS_OPTIONS、typdef enumとか)の書き換え。元々の定義のされ方によって変え方が色々
    • UIRemoteNotificationType だと .Alert みたいに短く書ける。謹製のは大体これでいける
    • UIActivityIndicatorViewStyleGray は UIActivityIndicatorViewStyle.Gray で。UIRectEdgeBottom も UIRectEdge.Bottom
    • SDWebImageCacheMemoryOnly は書き換えられなかったので SDWebImageOptions(1 << 2) とか
    • [receipt base64EncodedStringWithOptions:0]receipt.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(0))
    • appDelegate.fbSession.state == FBSessionStateCreatedTokenLoaded みたいなのはたぶん appDelegate.fbSession.state.value == FBSessionStateCreatedTokenLoaded.value
    • UIImagePickerControllerOriginalImage みたいなのは文字列なのでそのまま
  • #ifdef DEBUG みたいなのは Swift では使えないのであきらめる
  • シミュレータかどうかのチェックは
#if arch(i386) || arch(x86_64)
    // シミュレータ
#else
    // 実機
#endif
  • @throw したければとりあえず NSException(name:, reason:, userInfo:).raise()
  • Arrayの最後の要素がさわやかに取得したければ以下追加して Array.last して、どうぞ
extension Array {
    var last: T {
    return self[self.endIndex - 1]
    }
}
  • カテゴリはそのまま extension にする。プロトコル はそのまま protocol。
  • 親クラスのプロパティが id<何かのプロトコル> みたいなやつで、それを override したいとき
  • NSNotfound と比較しようとしたら ambiguous と言われて使えなかった。Int.max との比較でいいのかよく分かってない
  • 普通に小数を書くと Double になるっぽい。CGFloatで計算したい時はいちいち型指定しないと == とかでエラーになる。自分は面倒なのでとりあえず Double で計算して最後に CGFloat にしてしまった
  • String を整数にしたいときは String.toInt()。失敗した時のために if でチェック
    • __PRETTY_FUNCTION____FUNCTION__ にかえる。フォーマットも %s ではなく %@ なので注意
  • あと定型作業的なのは適当に書いた Python スクリプト (gist) で置換しました。全くないよりは多少捗るかと…
    • 上のスクリプトは正規表現で置換してるだけってところから察していただきたいですが、以下は間違いなく手作業で修正しないとだめです:
    • メソッドの引数の間のコンマ(特にブロックがらみのとこは置換できてない)
    • ブロックとか入ってる長くてややこしいメソッド呼び出し
    • クロージャの引数のタイプ(一括で消せないので残している)
    • initWithFrame みたいなのが (Frame:) に変換されちゃうので (frame:) にする
    • イニシャライザ系のもっと短く呼べるやつ ([UIColor colorWithRed:...] -> UIColor(red: ...) みたいなの)
    • override のシグネチャ書き換えたり as [クラス名] 追加したり
    • ()を消したり(基本とりあえずつけちゃってるので)
  • まあ最後ら辺のは Xcode が指摘してくれるのでそれに従って修正していけば多分なんとかなります

5.9. Storyboard でインスタンス化するクラスを設定しなおす

  • (Objective-CとSwiftのクラス名が同じでも)Storyboardで Custom Class の指定をしているところ(コマンド+オプション+3で出るやつ)をいったん削除して、Swiftのクラス名をいれる。やらないと実行した時にクラスが見つからないと怒られる

6. 起動して Optional のエラーに対応する

  • 起動して Optional.None が Unwrap できないというエラーに対応する(基本if文を追加するとかで)。起動する前に全部修正できるならした方がいいかもですが。。
  • IBOutletの接続先が存在しないのにアクセスしようとするとエラーになるので直す。そう、Swiftならね。
  • 辞書系はとりあえず if let 変数名 = 辞書[キー] as? 型 しておけば辞書に値が存在することとその型を両方チェックできるのでいい気がする。json もなんとかなる。ただネスト深くなりそう
  • 実機とかアドホックとか別のでもビルドできることを確認するなど

7. おしまい

  • Objective-C のクラスの方に引導を渡すなど

最後に

Objective-C を Swift に書き換えるのは手動だと面倒だし色々エラーではまって結構時間がかかりました。暇な人ならやってみるといいかもです。