inFablic | Fablic, inc. Developer's Blog.

フリマアプリ フリル (FRIL) を運営する Fablic の公式開発者ブログです。Fablic のデザイナー・エンジニア・ディレクターが情報発信していきます。

フリルをSwift 3.0移行した際に対応が大変だった箇所

こんにちは。エンジニアのshobyです。

フリルは先日リリースしたv6.5.0にて、Swift 3.0移行が完了しました。*1

ほぼ問題なく移行ができたのですが、移行作業中に対応が大変だった箇所がいくつかあったので共有します。 Xcode 8.3ではSwift 3.0移行が必須なため、まだ移行が済んでいない方にこの記事がお役に立てば幸いです。

なお、以下の環境はXcode 8.3.2、CocoaPods 1.2.0の環境を想定しています。

移行が大変だった箇所

  • CocoaPodsのSwift 3.0移行
  • Optional型の比較演算子が定義されなくなった
  • Implicitly Unwrapped Optionalの挙動変更
  • Closureのargument labelが使用できなくなった
  • 関数の返り値を使用していない場合warningが出るようになった

CocoaPodsのSwift 3.0移行

CocoaPodsをSwift 3.0移行に関しては移行黎明期のワークアラウンドを紹介する記事が多く、正しい方法が分からずに混乱しました。

以下の方法だけでうまくいったので、お試しください。

  • CocoaPodsを1.2.0以降に更新する
  • プロジェクト設定の SWIFT_VERSION を3.0に更新する

以上です。

新しいバージョンのCocoaPodsを使っていれば、プロジェクト設定からきちんとSwiftのバージョンを判定してくれます。

以下の方法は必要ありませんでした。

  • .swift-version というファイルをプロジェクトルートに置く*2
  • Podfileに swift_version = '3.0' を書く*3
  • Podfileの post_install フックで SWIFT_VERSION を上書きする*4

Optional型の比較演算子が定義されなくなった

Swift 3.0ではOptional型の >< といった比較演算子が定義されなくなりました。 そのため、挙動を維持したままNon-Optionalなコードに書き換えるのに苦労しました。

Converterを使うと以下のようなコードが各所に挿入されると思います。

// FIXME: comparison operators with optionals were removed from the Swift Standard Libary.
// Consider refactoring the code to use the non-optional operators.
fileprivate func < <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
  switch (lhs, rhs) {
  case let (l?, r?):
    return l < r
  case (nil, _?):
    return true
  default:
    return false
  }
}

上述したコードのように、 < の場合、前の値がnilかつ後ろの値が非nilの場合はtrue、前の値も後ろの値もnilの場合はfalseという挙動を維持しつつ、コードを書き換えるのは骨が折れると思います。

挙動が問題ないようであれば、optional bindingを使った式に変形するのをおすすめします。

let valueA: Int?
let valueB: Int

// Before(Swift 2.3)
if valueA < countB {
}

// After(Swift 3.0)
if let valueA = valueA, valueA < valueB {
}

Implicitly Unwrapped Optionalの挙動変更

Swift 3.0でImplicitly Unwrapped Optionalが型ではなく、IUO attributeのついたOptional型へと変更になったことで、SwiftからObjective-Cを使用している各所で変更が必要になりました。

Abolish ImplicitlyUnwrappedOptional type

Converterを使用した場合、各所でForced Unwrappingが行われ、SwiftLintのwarningが大量に発生することになります。

実質今までと挙動は変わりませんが、Optional Bindingを使用したコードに書き換えるのが適切だと思います。

Closureのargument labelが使用できなくなった

Swift 3.0ではClosureのargument labelが使用できなくなりました。

Remove type system significance of function argument labels

そのため、closureに多くの引数を渡しているケースではかなり可読性が悪くなってしまいました。

フリルでは、可読性を高めるため、closureに多くの引数を渡すのをやめ、非同期処理のcallbackに関しては、Resultを使った書き換えを進めています。

// Before(Swift 2.3)
completion(item: nil, error: error)


// After(Swift 3.0)
completion(nil, error) // 単なる書き換え

completion(.failure(error)) // Resultを使用した書き換え

関数の返り値を使用していない場合warningが出るようになった

Swift 3.0では関数の返り値を使用していない場合、warningが出るようになりました。

Defaulting non-Void functions so they warn on unused results

自前のメソッドの場合は @discardableResult のアノテーションを付けるだけで済みますが、UINavigationControllerのpopViewController(animated:) メソッドなどでもwarningが出るため、ViewController各所を以下のように書き換える必要があります。

_ = navigationController?.popViewController(animated: true)

まとめ

フリルでSwift 3.0移行時に大変だった箇所をまとめました。 CocoaPodsのSwift 3.0移行、Optional型の比較演算子がなくなったことによる修正、Implicitly Unwrapped Optionalの挙動変更に伴う修正、Closureのargument labelが使用できなくなったことによる修正、関数の返り値を使用していない場合warningが出るようになったことによる修正が大変でした。

現在移行を進めている方の参考になれば幸いです。

*1:厳密に言うとSwift 3.0.2です。また、次のv6.6.0でSwift 3.1に移行が完了しています

*2:ライブラリがSwiftのバージョンを指定するファイルです。使用する側では不要です

*3:ドキュメントにはどこにも書かれていませんでした。内部変数を上書きしているような気がします

*4:CocoaPods側の対応が終わっていなかったv1.1.0以前の手法です