Objective-C → Swift コンバータ3種を比較（Swiftify, iSwift, objc2swift）

Swiftが発表されて以来早2年、なんだかんだと言い訳しつつその後も大量のObjective-Cコードを生産し続けてまいりました。そんなカルマを精算すべく、**よく使いまわしているコードはSwift化** しておこう、と思うのですが、どうしても「**既に動いているコードを書き直す**」というのは、他の緊急性の高いタスクに割りこまれてしまいがち。。

そんなわけで、ObjC → Swiftに **自動変換** してくれるコンバートツールを試してみようと思い立った次第です。


##コンバータを探してみる

「確かヤフーさんが昨年コンバータをオープンソースで出してたなー」とググってみると、けっこう他にもいろいろとコンバータや変換方法が見つかりました。

###Swiftify

https://objectivec2swift.com/#/converter/code

オンラインで、**コードブロック／ファイル／プロジェクトの単位で変換** できます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/3180/48bad5c0-640e-a138-afc9-9b570744965e.jpeg">


[Pricing](https://objectivec2swift.com/#/pricing) というタブがあるので見てみたところ、無料でできるのは、

- コードブロック単位での変換
- 1回のコードサイズが10kBまで

のようです。

<img alt="swiftify_pricing.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/3180/9ff97913-37d6-ecd7-2696-8aaf12cc25a0.jpeg">


###iSwift

http://iswift.org/

オンライン版、ダウンロード版（Macアプリ）の2種類があるようです。オンライン版は、コードブロック単位での変換のみ。

<img alt="iswift_online.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/3180/8d368fca-0b8e-9457-f137-6422e6f7cbfe.jpeg">

- http://iswift.org/try

ダウンロード版は**試用期間1日**で、その後は有料（$14.99）となるようです。


###objc2swift

https://github.com/yahoojapan/objc2swift

昨年12月にヤフーさんが **オープンソースで公開** したコンバータ。

- [Obj-C → Swift コンバータをオープンソースで公開しました！ - Yahoo! JAPAN Tech Blog](http://techblog.yahoo.co.jp/ios/objc2swift/)

手軽に試せる[オンライン版](http://objc2swift.yahoo-labs.jp/)もあります。

<img alt="objc2swift.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/3180/d08da9c1-c5cd-c0b0-caa5-3642886ff11f.jpeg">

ソースコードを clone してきて gradle でビルドすれば、

```
$ git clone https://github.com/yahoojapan/objc2swift.git
$ cd objc2swift
$ gradle jar
```

こんな感じでコマンドラインからも変換できるようです。

```
$ objc2swift src/test/resources/sample.*
```

###Objective-C to Swift Converter

http://objc2swift.me/

こちらも国産、デジタルサーカス @tomzoh さんによる変換ツール。

メソッドコール単位での変換に対応しているようです。

- [Objective-C to Swift Converter を作りました – 長谷川智希 @tomzoh blog](http://www.hasegawa-tomoki.com/development/2015/04/17/objective-c-to-swift-converter/)

> objc2swift.meのバックエンドは文字列処理ベースの地味～で泥臭いプログラムだし、前述のとおりメソッドコールにしか対応していないので、いつか上記プロジェクトをバックエンドにしたみたいです。

（上記記述を踏まえ、今回は試しませんでした）

###その他、コード変換に関する参考記事

- [How can I automatically convert Objective-C to Swift? - Quora](https://www.quora.com/How-can-I-automatically-convert-Objective-C-to-Swift)
- [A-Liaison BLOG: レガシーな Objective-C プロジェクトを Swift なプロジェクトに変換する](http://akisute.com/2015/12/objective-c-swift.html)
- [Objective-CのコードをSwift化するXcode7の新機能 "Generated Interface" - Qiita](http://qiita.com/WorldDownTown/items/fc345cc8a90ad8dc3cf7)


##コンバートしてみる

今回Swiftへの自動変換対象として用いたコードは、以下のリポジトリにある TTMCaptureManager というクラス。

- https://github.com/shu223/SlowMotionVideoRecorder/

AVFoundation の動画の録画処理をラップしたクラスで、

```objc
self.captureManager = [[TTMCaptureManager alloc] initWithPreviewView:self.view];
[self.captureManager startRecording];
```

これだけで録画処理が書けて、フレームレートも簡単に変えられるようにしてあるので、たまーに引っ張り出してきて使ってます。

・・・が、2013年にプライベートでざっと書いたもので、なにぶん汚い。**1ファイルで600行**もある。リファクタリングしたいけど、**どうせならSwiftで書きなおしてからSwiftyにリファクタリングしたい**。

そんなわけでこのクラスを各コンバータにかけてみました。


###Swiftify

使ってみて気付きましたが、**サインアップ（無料）しないと1kBまでしかコンバート結果を表示してくれない**ようです。

で、サインアップして、変換してみた結果がこちら。

<img alt="swiftify_result.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/3180/49187f30-729a-bacc-d0f0-e873fcc3a7fe.jpeg">

一番上にこう書かれています。

```swift:swift
// The output below is limited by 10 KB.
// Upgrade your plan to remove this limitation.
```

変換結果の良し悪し以前に、**無料プランでの上限10kB**に引っかかってしまったのでした。長いコードこそ変換したいのに。。

変換の手間を考えると月15ドルは決して高いものではありませんが、無料で使える他のコンバータとの比較になるので、ここを実力とします。（正直なところ、変換されたコードを見ても、インデントが崩れてたりして、ちょっと不穏な感じが否めませんでした。。）


###iSwift

TTMCaptureManager.m を貼ると・・・エラー。

```swift:swift
/*
Unexpected :  <

Expected   :
'NULLABLE', 'NONNULL', const, volatile, 'NONNULL_', 'NULLABLE_', '+', '-', typedef, extern, static, auto, register, 'BLOCK___', 'WEAK___', void, char, short, int, long, float, double, Class, id, SEL, BOOL, signed, unsigned, #type, 'USER_TYPE_NAME', struct, union, '{', enum, 'NS_ENUM', 'NS_OPTIONS', @end, @property, 'COMMENT_MULTI', 'COMMENT_SINGLE'
*/
```

こういう無名カテゴリでのプロトコルへの適合がiSwift的にはダメみたいです。

```objc:TTMCaptureManager.m
@interface TTMCaptureManager () <AVCaptureFileOutputRecordingDelegate, AVCaptureAudioDataOutputSampleBufferDelegate, AVCaptureVideoDataOutputSampleBufferDelegate>
```


ではヘッダはどうだろう、と TTMCaptureManager.h を貼ってみると・・・エラー

```swift:swift
/*
Unexpected :  @

Expected   :
'NULLABLE', 'NONNULL', const, volatile, 'NONNULL_', 'NULLABLE_', '*', typedef, extern, static, auto, register, 'BLOCK___', 'WEAK___', void, char, short, int, long, float, double, Class, id, SEL, BOOL, signed, unsigned, #type, 'USER_TYPE_NAME', struct, union, #identifier, enum, 'NS_ENUM', '(', 'NS_OPTIONS', @implementation, @interface, @class, #ifdef, #ifndef, #ident, #include, #undef, #import, #define, #pragma, #if, 'ELIF__', #else, #endif, 'COMMENT_MULTI', 'COMMENT_SINGLE'
*/
```

`@import` がダメみたいです。

```objc:TTMCaptureManager.h
@import CoreMedia;
```

これならどうだ、とヘッダの `@interface` 部分だけ貼ってみたところ、

```objc:TTMCaptureManager.h
@interface TTMCaptureManager : NSObject

@property (nonatomic, assign) id<TTMCaptureManagerDelegate> delegate;
@property (nonatomic, readonly) BOOL isRecording;
@property (nonatomic, copy) void (^onBuffer)(CMSampleBufferRef sampleBuffer);

- (instancetype)initWithPreviewView:(UIView *)previewView
                preferredCameraType:(CameraType)cameraType
                         outputMode:(OutputMode)outputMode;
- (void)toggleContentsGravity;
- (void)resetFormat;
- (void)switchFormatWithDesiredFPS:(CGFloat)desiredFPS;
- (void)startRecording;
- (void)stopRecording;
- (void)updateOrientationWithPreviewView:(UIView *)previewView;

@end
```

やはりエラー・・・

```swift:swift
/*
Unexpected :  #identifier ('CMSampleBufferRef')

Expected   :
'NULLABLE', 'NONNULL', const, volatile, 'NONNULL_', 'NULLABLE_', typedef, extern, static, auto, register, 'BLOCK___', 'WEAK___', void, char, short, int, long, float, double, Class, id, SEL, BOOL, signed, unsigned, #type, 'USER_TYPE_NAME', struct, union, enum, 'NS_ENUM', ')', 'NS_OPTIONS'
*/
```

ちなみに試用期間1日のダウンロード版（Macアプリ）の方で試してみても、同様の結果でした。


###objc2swift

手っ取り早く変換結果を見れるweb版で試しました。

最初、TTMCaptureManager.m のコードを貼ってみたところ、**コンバート結果が出ない**。。

まさか、これもダメなのか・・・とあきらめかけたところ、

```objc
@interface MyClass

- (void)sayHello;

@end

@implementation MyClass

- (void)sayHello{
    NSLog(@"Hello Swift, Goodbye Obj-C!");
}

@end
```

デフォルトで入っているこのサンプルコードはちゃんと変換されている模様。

どうやら、**ヘッダの内容 `@interface` と実装ファイルの内容 `@implementation` をセットで** 渡さないといけないようです。

というわけでヘッダの内容をコピペしつつ、import とかの余計な部分を削除したところ、

<img src="https://qiita-image-store.s3.amazonaws.com/0/3180/d9b9afdb-c906-b9ff-e4a9-c56d15aa8959.jpeg">


ばっちりコンバートされました。生成されたコードをざっと見ても、`private` とかも判別されていて、なかなか良さげ。そのままビルド成功、というわけにはさすがにいきませんが、これだけやってくれればかなり捗りそうです。


##まとめ

3種類の Objective-C → Swift コンバータを試しました。

- **Swiftify**: 無料プランでは10kB制限により1クラス全体を変換できず
- **iSwift**: エラーでまったく変換されず
- **objc2swift**: なかなか良さげ

というわけで、唯一まともに動いたのは objc2swift だけで、しかも完全無料でオープンソース。現状では **ObjC → Swift コンバータは objc2swift 一択** という結果になってしまいました。

が、今回は1クラス試しただけだし、得意不得意とかもあるのかもしれません。今後もいろいろなソースコードで試してみたいと思います。