Try swift amy dyer fb

徐々にSwiftに移行する

Objective-Cで書かれた既存のアプリを、Swiftに移行しなければならなくなったとき、どうしますか?このtry!Swiftの講演では、Estyをケーススタディとして、アプリを徐々にSwiftへの移行する際の方針を示します。

SwiftはObjective-Cとの相互運用において優れた機能を提供していますが、現在のコードベースに適用することが必ずしも良いとは言えません。この講演ではLintや依存性の管理などの技術的な詳細や、サポートを集めるための組織戦略、その他Estyを通して学んだことをカバーしています。

これを読んだあとは、あなたのコードも会社も、Swiftにスムーズに移行する準備が整うでしょう。


イントロダクション (00:00)

アプリを徐々にSwiftに移行することについてお話しましょう。今日の内容はEstyから得た経験をケーススタディとしています。今日話すことは、

  • どこから始めて、Swiftでどうしていけばいいのか
  • コードベースに入れて得た経験
  • 学んだことやつまずいたこと

Estyは世界中の人々がオンラインやオフラインでユニークなものを作ったり、売ったり、買ったりする世界市場です。Estyには4つのネイティブアプリがあります。買う人のためのアプリ、売る人のためのアプリが、iOSとAndroidの両方にあります。

Swift (00:35)

2015年にSwift 2.0が登場し、オープンソースとなったとき、Swiftは魔法に見えました。私を含むEstyのエンジニアは、Swiftをどうやって使っていけるのか、ワクワクしました。

クラスを見て思いました。「ジェネリクスを使って書き直したらもっと良くなるだろうなぁ」とか、「コンパイルタイムのAPIが使えたら、この機能はもっと楽に書けるのになぁ」と。そして、自問自答し始めました。Objective-Cに留まるか、すべてを書き直すのかと。

色んな会社からすごい記事が飛び出しました。ひとつはLyftから出たもので、Swiftへの移行を早く、コスト低く、最低限のバグで行う方法についてでした。この記事に多くの開発者が喜びました。これらすべてが魔法に見え、ワクワクさせてくれました。これも待ち望んでいたものでした。

「すごい、じゃあ書き直そう」と思い、私たちは時間をかけてプロポーザルをまとめることに決め、アプリを徐々にクラスごとに移植しました。もちろん、まずはコンセンサスを構築するところからでした。私が驚いたのは、iOS以外の人々とSwiftを使い始めることについて話したとき、私は2つのコメントをもらいました。

「Swiftが出て一年ぐらいたつけど、まだ使ってないのか?何か問題があったのか?」と言われたり、「頭おかしいんじゃないの?なんでアプリをスクラッチから書き直す必要があるの?」と言われると思います。コンセンサスを取ることはこのプロセスにおいて重要で、私たちがそれをすることに決めたのは、アーキテクチャレビューと呼んでいるプロセスを通してでした。つまり、プロポーザルを書いて、部屋に偉い人を連れていき、なぜ新しいコードを書くのか説明するという負担はありますが、あなたのアイディアは生活をより良くします。

私達にはこんな賢い人たちがいて、そのうちの一人が聞いてきました。「あなたの書いたSwiftのコードがすでに書いてあるObjective-Cのコードよりいいってどうやってわかったの?」と。

記事の更新情報を受け取る

これは良い質問でした。なぜなら、実際Objective-Cのコードが大量にあったからです。2016年8月の時点で、以下のような状態でした。

  • 5年分のコミットログ
  • 280,000行のObjective-Cのコード
  • 2,500個の実装ファイル(ライブラリを除く

これを持ち出したのは、これが悪いことだからではありません。すべてのコードはObjective-Cの多くの経験と権威と専門知識があることを表しています。そのすべてを投げ捨ててからやり直すことは、あまり慎重なアプローチではなかったかもしれません。質問に対する答えを見つける必要があると考えました。Swiftを使用する理由が必要でした。それは、Swiftが魔法でクールで刺激的であったからだけではありませんでした。

私たちは一歩踏み出すことにしました。「私たちは一つの世界に住んでいなくてもいいでしょう。すぐにSwiftでテストを書き始めましょう。それは私たちが3〜4か月間したこととまったく同じことです。Swiftに機能テストと単体テストを書き始めました。それを質問に答える手段として使っています。

安定と戦略 (03:22)

2つの言語を行き来するとき、Objective-Cを見て、特定のことを表現できないことに気付き始めます。関数を書きます。それを見ます、そして、この引数がnilだったらどうなりますか?多分すべてが壊れませんか?

そのことから答えを得ました。 「私たちはSwiftの言語仕様がクールで流行っているから使いたいわけではない。 Swiftを使用したいのは、より安全なコードを書こうと思っているからだ」と。そこから私たちはコンセンサスをもう一度作り上げることができました。

私たちは新たな提案で別のアーキテクチャーレビューをしていました。私たちはすべてをSwiftに移行するのではなく、Swift開発を必須にするという新たなゴールを話しました。

私たちが書いたSwiftのコードがより良く、より安全になると信じていれば、Swiftで新しい機能を書いて、すでにわかっていて信頼できる古いObjective-Cをすべて保持することができます。それが私たちがたどり着いたところです。

「Objective-CやSwiftを選ぶのではなく、2言語のコードベースにしてみましょう。」と言いました。実際にはスマートな決断だったと私は考えています。なぜなら、アプリを一度に1つずつ移植するのであれば、とにかくSwiftに完全に移行するまでには数年かかったからです。

短所と長所 (04:27)

このアプローチにはたくさんの短所があります。つまり、コードベースの中間の面倒な状態にいるということです。

Xcodeはどの言語を扱っているのかを忘れ、間違ったヘッダを表示します。もっと重要なことは、開発者は2つの言語を理解している必要があります。これも教育面でよくありません。そして最後に、Briding HeaderやAuto-generated Interfaceのような、面倒な相互互換機能すべてに対処しなければなりません。

しかし、私たちにとっては、長所は短所を上回っていました。Swiftを少しずつ取り入れると、リスクに対応する時間が増えます。それは、Swiftがどのように動作しているのかも理解することにつながります。そして最後に、それは組織でSwiftを学ぶ時間が得られます。したがって、Swiftをどんどんかけば、より良いSwiftのプログラマになれます。

Swiftをどのように使い始めるか、アプローチが必要でした。私たちがたどり着いたアプローチは実験でした。

Swiftの実験 (05:09)

コードベースにSwiftを追加する方法についての仮説を立て、次にこれをテストする方法を探します。

これは重要なことでした。何かが壊れていることが分かっていたため、壊れているものがユーザーやアプリの生産性に影響していないことを確認したかったのです。Swiftは不安定であることも含めて、さまざまな理由で問題が勃発していました。

Swiftが活発な開発の下にあるという意味では不安定であり、Appleは依然として非互換の変更を続けています。 XcodeやPlaygroundがクラッシュすることもあります。

実際に問題を引き起こしていないことを確認したいのです。

また、アプリがより大きなエコシステムの一部であることに気づきました。私たちは良いリポジトリだけではなく、サードパーティのサービスも使っています。クラッシュログの取得や、Appleにビルドを提出する必要があります。そのためにビルドマシンも使っていいます。(することになるとはあまり思ってませんでしたが)翻訳も必要です。

私たちにとって、アプリに関わるこれらの外部のなサービスのそれぞれに、Swiftを追加することがどのように影響を与えるのかを尋ねることが重要でした。

それから3つの目標を思いつきました。

  1. Swiftのランタイムを追加する。
  2. 最初のSwiftクラスを追加し、A/Bテストを行う。
  3. Swiftで新しい機能の開発を行う。

Swiftのランタイムの追加 (06:29)

Swiftは安定したバイナリインターフェイスではないため、Swiftのアプリのリリースしようとすると、実際にはSwiftのライブラリのすべてがバンドルされます。

たくさんのボックスをチェックすると、実行されないコードがいくつかバンドルされることになりました。 Swiftに隠れたView Controllerを追加しました。

私たちが学んだ最初の重要なことは、それらのライブラリが実際にそこにあることを確認することです。 SwiftアプリケーションでIPAファイルを展開すると、 SwiftSupport と呼ばれるペイロードの下にフォルダが表示されます。それはswiftlibcore.dlibのようなダイナミックライブラリでいっぱいになるはずです。

これらのライブラリがない場合、Appleはあなたに厄介なメールを送り、アプリをリジェクトします。落ち着いてIPAファイルを展開してください。(Xcodeビルドが含まれている)特定のヘッドレスビルドには、デフォルトでこのフォルダが含まれていないことが判明しました。 WatchKitのアプリにも同じ不具合があります。

私たちが学んだことのもう一つは、ビルドサイズのモニタリングです。 Swiftランタイムのこれらのライブラリは、17MBもの容量を追加しています(相当な量です)。

結局のところ心配する時間を無駄にしました。ダウンロードに悪い影響を及ぼす、OTAでのダウンロード制限が私たちに打撃を与えることについてです。App Storeにアプリを提出するときに私たちのアプリがどれくらい大きくなるのかを教えてくれるスクリプトを思いつくのに時間を費やしました。App Thinningなどの理由で難しいことが判明し、Appleはそれを圧縮しています。アプリのサイズがどれだけ大きいかという質問に答えることはできません。

解決策はこれです。アプリのバイナリをiTunes Connectにアップロードしてください。すべてのメニューの中で、アクティビティ、すべてのビルド、そしてビルドをクリックすると、アップルはユーザーにリリースするアプリのサイズを正確に伝えます。

最初の実験が成功した後、最初のSwiftコードを実行することにしました。

最初のSwiftのコードを実行する (08:15)

これが2つ目の実験です。使うアプローチはA/Bテストです。一行ずつ、既存の単純なView Controllerを書き直しました。Swiftに新しいコードを書く能力をテストしていなかったこと、Swift自体をテストすること、そしてそれがObjective-Cコードベースの他の部分とどのように相互作用しているかを知ることに役に立ちました。

この実験でさらに新しいことがわかりました。 クラッシュしたのです

簡単なクイズを用意しました。どの部分でクラッシュが発生するでしょうか。


guard let collection  = self.collection else { return }
let isPrivate = collection.isPrivate()
let isFavorites = collection.type == "favorites"

正解は、3番目のラインのcollection.typeというプロパティにアクセスしたときです。

ここで何が起こったかというと、collectionはObjective-Cのクラスのインスタンスですが、nullable識別子を追加していませんでした。これは、collection.typeが暗黙的アンラップされたString型になったということです。暗黙的アンラップをされたオプショナル型です。なので、この効果的なオプショナルの値は、アクセスしたときに自動的に強制アンラップされます。

ファイルにアノテーションをつける というのは重要な教訓でした。もちろん、2,500個のヘッダファイルのようなもので、それらすべてにアノテーションを付けることは全く実用的ではないので、これは興味深いものでした。

私たちがたどり着いた解決策は、ファイルをインポートしている間にファイルにアノテーションを付けることでした。 Swift Bridging HeaderはSwiftとObective-Cの間のファイアウォールであり、そこにアノテーションをつけておく限りは安全です。

特別な注意が必要ですが、見出しをインポートするヘッダのネストがあることを忘れないでください。リンクするすべてのヘッダでSwift Annotationを使用できるようにする必要があります。

私たちが学んだことがもう一つあります。クラッシュロガーはSwiftで役に立つ情報を与えてくれませんでした。コードのどこでクラッシュしているのか把握しようとしていましたが、スタックトレースからランダムなゴミを受け取りました。

Swiftの興味深い特徴がわかりました。適切な名前空間を持っていることにより、リンカレベルのコンパイルに名前の衝突がないことを確認していなかったことです。Swiftのすべてのシンボルはこの混乱したフォーマットにコンパイルされました。

XcodeにはSwift-demangleという便利なツールがあります。スタックトレースをSwift-demangleに入れると見た目が適切なスタックトレースを得ることができます。クラッシュが直ったので、Swiftに新しいコードを書く準備が整ったと判断しました。

Swiftで新しいコードを書く (11:10)

チームのゴールを思いつきました。他の開発者がObjective-Cから使うために書き直さなければいけないようなSwiftのコードは書かないということです。

これを言う理由は、コードの再利用が重要なゴールであると判断したからです。誰かがSwiftに新しい、エキサイティングなユーティリティを書いてくれて、他の開発者がObjective-Cの半分のコードでそれを使用したいと思っているけどできないという状況に納得していませんでした。

Swiftの多くの機能がObjective-C(Generics、Tuples、Structures)と後方互換性がないので、実際には難しいです。あなたがSwiftのコードにそれらがあるなら、Objective-Cのヘッダに自動生成され、それらをすべて含まないだけです。

私たちがたどり着いた解決策は簡単でした。アクセスレベルを使用してください。Swift3.0にはfileprivate属性とprivate属性があります。私たちが決めたのは、たとえばジェネリックまたは構造体を使用する場合は、privateのフラグが立てられていることを確認することです。非常にSwiftyなコードを書くことができますが、そのためにインターフェイスとなります。 Objective-Cから引き続き使用できるものを書く必要があります。

しかし、これをどうやって強制させるのでしょう?私たちが用いたアプローチは、Linterを使うことでした。

Linterはソースコードを入出力のスタイル違反(コンマやカッコ)の検出に使用する小さなソフトウェアです。これを使ってもっと強力なことをすることができます。私は SwiftLintと呼ばれる偉大なオープンソースプロジェクトを使って、Lintルールを書きました。


Code/Swift/Interoperability.swift:19:2: warning: Objective-C Interoperability Violation: Object someFunction(_:_:) of type FunctionFree should be private, but is internal (objective_c_interoperability)

Lintルールは、パブリックアクセスが可能な方法でSwiftのみの機能を使用していないことを確認します。そうした場合、警告が表示され、そのコードをコードベースから守ることができます。

最後に、私たちが学んだことは、Objective-Cコードの多くがSwiftから見ると良くないものに見えるということです。あまりSwiftyではありません。そして、NS_REFINED_FOR_SWIFTというすばらしいマクロがあります。

醜いObjective-Cコードがある場合は、このマクロでタグ付けし、Swiftにextensionを書き込むことができます。


@interface MyClass : NSObject
- (void)anUglyFunction NS_REFINED_FOR_SWIFT;
@end

extension MyClass {
  public func aPrettierFunction() -> Void {
    return self._anUglyFunction()
  }
}


これにより、Swiftがその関数をどのように見ているのかを再定義することができます。これが便利なアプローチだと思ったら、このすばらしい講演もみてくださいWWDC 2015 called “Improving your existing apps with Swift.”

教育、安定化、そして未来へ (13:22)

Swiftの書き方が、既にあるObjective-Cのコードと同じかそれ以上のものになるかどうかを確認するにはどうしたらいいのでしょうか?

私たちが必要としたのは、コーディングスタンダードでした。私たちが書いたSwiftのコードに同意したことを確認したかったのです。

面白いことに、私たちの誰もが自分たちのSwiftのコーディングスタンダードをスクラッチから書く権利がないと感じていました。これは私たちにとっても新しい言語でした。私たちがしたこと、そしてあなたへの提案は、もちろん、借りてくることです。

多くの企業がSwiftのコーディングスタンダードを公開しています。私たちはGitHubで始めました。その出発点をとって、私たちが今あるコーディングスタンダードに到達するために、相互互換に関する懸念を持って修正を始めました。他にも、もちろん、新しいバージョンを標準化することを忘れないでください。

Swiftの開発者が何人かいるなら、Xcodeのバージョンが一致していない可能性があります。Objective-Cでは問題ありませんが、Swiftでは問題になります。

あなたはリリースコードを書き始めようとしていて、お互いに違う文法で始めるでしょう。違うバージョンで動いているので、コンパイルできません。

コマンドラインから、実行しているSwiftのバージョンを確認し、ただちに合わせることができます。 Swift.orgは、Xcodeのバージョンとは別に、Xcodeにインストールできるツールチェーンを公開するようになりました。あるいは、Xcodeを同時にインストールすることもできます。

それから、もちろん、Swiftのこれからのバージョンに対応しなければなりません。 Swift 3.0を採用すると、最初にSwiftを採用する場合と同じ問題が潜在的に発生します。それは破壊的な変更です。 私たちはどうやって対処すればいいのでしょう? 私たちは、継続的にベース上で同じ実験的アプローチをとることに決めました。

ビルドマシンの1つにXcodeの次のバージョンをインストールするように、コード移行ツールを使ってブランチでSwift 3.0を実行することを考えました。これにより、同じ実験的アプローチを使用して、Swift 3.0が問題を起こさないようにすることができます。

最後に、私たちは教育を受けています。これは現在進行中のことです。iOS以外の言語の開発者をiOSアプリにコントリビュートさせるにはどうすればよいでしょうか?私たちは、Swift人口を増やすために、ワークショップやランチを行うアプローチを取っています。

より大きな絵を (15:23)

この話を聞いていているみなさんが、巨大なObjective-Cのプロジェクトを持っていて、明日からSwiftを使い始めるとしたら、あなたにできるアドバイスは何で、どこから始めることができるのでしょうね。

まず最初に、あなたが必要としている理由を見つける必要があります。それは重要な機能だったり、あなたがSwiftを使う理由です。 逃げることを怖がってはいけません。開発者やユーザーにとって良いものである必要があります。

その理由が分かれば、他の人に伝え始めます。あなたがコードをSwiftで書いて、他の人を台に乗せる方がはるかに簡単になります。

次の提案は、コードベース外で始めることです。私たちにとっては、テストを書くことから始めるのは非常に重要でした。重要なことの1つは、Swiftを書くことで経験を得られることです。またもう一つ重要なことは、EtsyをSwiftで書いて、それが自分たちにとって何を意味するのかを理解することができるということです。

テストやツール、Playgroundのプロジェクトなど、コードベース外から始めると、グループで学ぶ機会が与えられます。

その次はもちろん、テストの仮説を立てることです。私たちのために壊れたもの、あなたのために壊れるかもしれないもの、おそらく新しいものも見つけるでしょう。すべてのシステムが異なり、すべてのシステムがユニークです。何が問題になるのかを理解する唯一の方法は、それを試すことです。

私の提案は、ダイアグラムを作成し、アプリに触れるすべてのものを見つけ出し、”Swiftがあれば、これは変わるのだろうか“、または “これはどのように変化するのだろうか“と自問自答することです。そして、それらすべてについて、本番環境のアプリで壊れることなく、実際にテストできる方法を見つけるための実験をやってみてください。

やればできます。相互互換性のあるこの2つの非常に強力な言語を扱うことができて幸いです。他にないようなツールを提供してくれます。始めるのを怖がってはいけません。

ありがとうございました!

参考資料

About the content

2016年9月のtry! Swift NYCの講演です。映像はRealmによって撮影・録音され、主催者の許可を得て公開しています。

Amy Dyer

Amy Dyer

ニューヨーク、ブルックリンにあるEtsyという会社でソフトウェアエンジニアとして働いています。iOSデベロッパーとしての経験は5年で、現在はEstyのショッピングアプリケーションの開発に携わっています。

4 design patterns for a RESTless mobile integration »

close