Swiftが発表されて以来早2年、なんだかんだと言い訳しつつその後も大量のObjective-Cコードを生産し続けてまいりました。そんなカルマを精算すべく、よく使いまわしているコードはSwift化 しておこう、と思うのですが、どうしても「既に動いているコードを書き直す」というのは、他の緊急性の高いタスクに割りこまれてしまいがち。。
そんなわけで、ObjC → Swiftに 自動変換 してくれるコンバートツールを試してみようと思い立った次第です。
##コンバータを探してみる
「確かヤフーさんが昨年コンバータをオープンソースで出してたなー」とググってみると、けっこう他にもいろいろとコンバータや変換方法が見つかりました。
###Swiftify
オンラインで、コードブロック/ファイル/プロジェクトの単位で変換 できます。
Pricing というタブがあるので見てみたところ、無料でできるのは、
- コードブロック単位での変換
- 1回のコードサイズが10kBまで
のようです。
###iSwift
オンライン版、ダウンロード版(Macアプリ)の2種類があるようです。オンライン版は、コードブロック単位での変換のみ。
ダウンロード版は試用期間1日で、その後は有料($14.99)となるようです。
###objc2swift
昨年12月にヤフーさんが オープンソースで公開 したコンバータ。
手軽に試せるオンライン版もあります。
ソースコードを clone してきて gradle でビルドすれば、
$ git clone https://github.com/yahoojapan/objc2swift.git
$ cd objc2swift
$ gradle jar
こんな感じでコマンドラインからも変換できるようです。
$ objc2swift src/test/resources/sample.*
###Objective-C to Swift Converter
こちらも国産、デジタルサーカス @tomzoh さんによる変換ツール。
メソッドコール単位での変換に対応しているようです。
objc2swift.meのバックエンドは文字列処理ベースの地味~で泥臭いプログラムだし、前述のとおりメソッドコールにしか対応していないので、いつか上記プロジェクトをバックエンドにしたみたいです。
(上記記述を踏まえ、今回は試しませんでした)
###その他、コード変換に関する参考記事
- How can I automatically convert Objective-C to Swift? - Quora
- A-Liaison BLOG: レガシーな Objective-C プロジェクトを Swift なプロジェクトに変換する
- Objective-CのコードをSwift化するXcode7の新機能 "Generated Interface" - Qiita
##コンバートしてみる
今回Swiftへの自動変換対象として用いたコードは、以下のリポジトリにある TTMCaptureManager というクラス。
AVFoundation の動画の録画処理をラップしたクラスで、
self.captureManager = [[TTMCaptureManager alloc] initWithPreviewView:self.view];
[self.captureManager startRecording];
これだけで録画処理が書けて、フレームレートも簡単に変えられるようにしてあるので、たまーに引っ張り出してきて使ってます。
・・・が、2013年にプライベートでざっと書いたもので、なにぶん汚い。1ファイルで600行もある。リファクタリングしたいけど、どうせならSwiftで書きなおしてからSwiftyにリファクタリングしたい。
そんなわけでこのクラスを各コンバータにかけてみました。
###Swiftify
使ってみて気付きましたが、サインアップ(無料)しないと1kBまでしかコンバート結果を表示してくれないようです。
で、サインアップして、変換してみた結果がこちら。
一番上にこう書かれています。
// The output below is limited by 10 KB.
// Upgrade your plan to remove this limitation.
変換結果の良し悪し以前に、無料プランでの上限10kBに引っかかってしまったのでした。長いコードこそ変換したいのに。。
変換の手間を考えると月15ドルは決して高いものではありませんが、無料で使える他のコンバータとの比較になるので、ここを実力とします。(正直なところ、変換されたコードを見ても、インデントが崩れてたりして、ちょっと不穏な感じが否めませんでした。。)
###iSwift
TTMCaptureManager.m を貼ると・・・エラー。
/*
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的にはダメみたいです。
@interface TTMCaptureManager () <AVCaptureFileOutputRecordingDelegate, AVCaptureAudioDataOutputSampleBufferDelegate, AVCaptureVideoDataOutputSampleBufferDelegate>
ではヘッダはどうだろう、と TTMCaptureManager.h を貼ってみると・・・エラー
/*
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
がダメみたいです。
@import CoreMedia;
これならどうだ、とヘッダの @interface
部分だけ貼ってみたところ、
@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
やはりエラー・・・
/*
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 のコードを貼ってみたところ、コンバート結果が出ない。。
まさか、これもダメなのか・・・とあきらめかけたところ、
@interface MyClass
- (void)sayHello;
@end
@implementation MyClass
- (void)sayHello{
NSLog(@"Hello Swift, Goodbye Obj-C!");
}
@end
デフォルトで入っているこのサンプルコードはちゃんと変換されている模様。
どうやら、ヘッダの内容 @interface
と実装ファイルの内容 @implementation
をセットで 渡さないといけないようです。
というわけでヘッダの内容をコピペしつつ、import とかの余計な部分を削除したところ、
ばっちりコンバートされました。生成されたコードをざっと見ても、private
とかも判別されていて、なかなか良さげ。そのままビルド成功、というわけにはさすがにいきませんが、これだけやってくれればかなり捗りそうです。
##まとめ
3種類の Objective-C → Swift コンバータを試しました。
- Swiftify: 無料プランでは10kB制限により1クラス全体を変換できず
- iSwift: エラーでまったく変換されず
- objc2swift: なかなか良さげ
というわけで、唯一まともに動いたのは objc2swift だけで、しかも完全無料でオープンソース。現状では ObjC → Swift コンバータは objc2swift 一択 という結果になってしまいました。
が、今回は1クラス試しただけだし、得意不得意とかもあるのかもしれません。今後もいろいろなソースコードで試してみたいと思います。