読者です 読者をやめる 読者になる 読者になる

トレタのiPadアプリをSwift 3 対応しました

iOS Swift

iOSエンジニアの高(@y_koh)です。

この度トレタではiPadアプリのSwift 3対応を行いました。どんな感じで進めたのかと、ハマったところなど共有できればなと思います。

対応自体は去年末には終えていましたが、年が明けて1/10にリリースしました。 年末は飲食店さま繁忙期のため、トレタではこの時期のアプリアップデートは控えています。例年このタイミングでリファクタリングやKaizenタスクなどを行っています。今回はSwift 3対応をメインに行いました。

先日サーバサイドもRailsを4.2にバージョンアップしています。言語やフレームワークのバージョンアップは機能改善に直接つながるものではないので後回しにしがちですが、将来的に負債になってしまうだけなので出来る限り時間を作って適宜アップデートするようにしています。

今回のSwift 3対応については、昨年のpotatotips#35でも発表させていただきました。 speakerdeck.com

概要

コード行数

9万行ほど

Swift使用率

Screen Shot 2016-11-26 at 12.48.04.png (21.8 kB)

トレタのベースはObjective-Cです。Swiftの占める割合は全体の2割ほどですが、コアなUI部分からSwift化しているので割りと複雑なところが多いです。基本的なスタンスとして新規追加もしくはリファクタリングを行うタイミングでSwiftを使用するようにしています。

その結果APIやモデル周りがまだObjective-Cのままなので、今後はこちらを設計見直しと合わせて対応していければと思っています。

期間

2〜3週間ほどかかりました。

進め方

こんな流れで進めていきました。

  1. 作業ブランチを切る
  2. ライブラリをアップデートする
  3. XcodeでSwift 3にConvertする
  4. コンパイルエラーをひたすら潰す
  5. 動作確認

作業ブランチを切る

iPadアプリはエンジニア3名体制で開発しているので他の開発と並行して作業することになります。feature/swift3ブランチで作業して、developに加わった変更を随時取り込んでいくことになります。

当然ながらdevelopから離れれば離れるほど大変になるので、迅速な対応が求められます。

ライブラリをアップデートする

ライブラリ自体がSwift 3対応している必要があるので対応状況を調査するところから始めます。 READMEにしっかり書いてあるものもあれば、何も書かれておらずブランチのみ用意されているものもあります。最悪メンテされていない場合は他のライブラリに乗り換える必要も出てきます。

時期的にもほとんどのライブラリは対応されていると思います。もし現状対応されていなければ乗り換えを検討されたほうが良いかもしれません。

またSwiftライブラリだけでなく、Objective-Cライブラリもnullabilityアノテーションに対応されているケースもあるので確認してみると良いと思います。

XcodeでSwift 3にConvertする

convertしようとするとこんな感じアラートが出て来てconvert出来ずにしばらく悩みました。

f:id:y_koh:20170126154245p:plain

http://stackoverflow.com/questions/39492974/tests-stop-working-under-xcode-8-test-host-error

こちらにあるように、テストターゲットのTesting > Target ApplicationNoneにすることで対応できました。

コンパイルエラーをひたすら潰す

ここからはコンパイルエラーをひたすら潰していきます。エラー数が多いとコンパイラが途中で諦めてしまうため、エラー数の総量を知ることが出来ません。よって、エラーを解消して残エラー数が減っていくとあるところでガッツリ増える、ということを繰り返してくことになります。

これに関してはどうすることも出来ないので諦めない心で頑張るしか無いです。コツとしては、まず全体を知るために、エラー解消が難しいところはちゃんとメモに残した上で、コメントアウトしてでも一旦通すのが良いと思います。

主要ライブラリ対応

SwiftBond

Migration from v4.x to v5.0を参考にMigrationします。

observeNewがしれっと無くなっているのでskipで代用します。

hoge.skip(first: 1).observeNext

また、bnd_notificationの場合は定義時にはクロージャは走らなくなっているのでskipする必要はありません。これに気づかずなぜか一回目だけ挙動がおかしいというのでハマりました。。

ライブラリ以外での変更

DispatchOnce

Swift 3 からはdispatch_onceに相当するものが提供されなくなりました。 こちらはlazy initialized globalsもしくはstaticプロパティで代用できます。

こちらの記事に詳しくまとめられていたので参照してみると良いと思います。 Swift3のGCD周りのまとめ - Qiita

Date

Swift 3からNSDateのSiwft実装Dateが提供されるようになりました。これにより、MTDatesというObjective-Cライブラリを使用していたのですが、NSDateのカテゴリ実装のためDateでは使えません。よってこの様にキャストする必要があります。

この時はまずビルドを通すことを目標にしていたので、地道に書き換えてましたが、後にブリッジするためのDate Extensionを作ってます。

(reservation.startAt as NSDate).tr_startOfToday())

CGFloat の剰余

これは少し驚いた変更でした。

y % heightByHour
↓
y.truncatingRemainder(dividingBy: heightByHour)

truncatingRemainder(dividingBy:) - FloatingPoint | Apple Developer Documentation

Int型の場合はいままでどおり%が使えます。

@escaping

Swift 3 の @escaping とは何か - Qiita

@noescapeがdeprecatedになり、逆に@escapingが必須になりました。

@escapingの有り無しで別メソッド扱いになるみたいで、interfaceに@escapingを付け忘れたときに呼べないということがありました。

removeRange

Screen Shot 2016-11-26 at 13.03.49.png (48.5 kB)

エラー内容がわかりにくくてちょっと悩みました。

courseLabels.removeRange(CountableRange<Int>(courses.count ..< courseLabels.count))
↓
courseLabels.removeSubrange(CountableRange<Int>(courses.count..<courseLabels.count))

Swift 3のRange徹底解説 - Qiita

iOS 10対応

ユーザーデータアクセス

写真選択時にエラーになりました。

This app has crashed because it attempted to access privacy-sensitive data without a usage description.  The app's Info.plist must contain an NSPhotoLibraryUsageDescription key with a string value explaining to the user how the app uses this data.

iOS 10 からInfo.plistにユーザーデータへアクセスする目的の記述が必須になっているのでこちらを対応しましょう。

[iOS 10] 各種ユーザーデータへアクセスする目的を記述することが必須になるようです | Developers.IO

その他

テストが動かない

Could not determine bundle identifier for xxxTest TEST_HOST: some path that does not exist

こんなエラーが出てきましたが、テストターゲットのHostApplicationを切り替えてみたら動きました。

IUOが無くなった影響で、文字列内式展開でOptionalが出力されてしまった

ラベルにそのまま"Optional(y_koh)“のように表示されてしまいました。これはUnwrapすることで対応しました。

var user = User() // modelはObjective-Cでnullabilityアノテーション指定なし
var label = UILabel()
label.name = user.name

var user = User() // modelはObjective-Cでnullabilityアノテーション指定なし
var label = UILabel()
if let user = user {
    label.name = user.name
}

Swift 3 対応時にハマったString Interpolate - Qiita

iOS 9でクラッシュ

Swift 3というかSDKの違いかもしれません。

let cell: TableLayoutListCell = tableView.dequeueReusableCell(withIdentifier: "ReservationCell", for: indexPath) as! TableLayoutListCell

上記を実行すると、

  • iOS 9 layoutSubviewsが呼ばれる
  • iOS 10 layoutSubviewsが呼ばれない

となって挙動が変わっています。今回は先にlayoutSubviewsが呼ばれることを想定していないコードになっていたのでUnwrap時にnilで落ちていました。

まとめ

Swift 3対応はアプリの規模にもよりますが、かなり根気のいる作業となります。これから対応される方は最初から完璧にやろうとせずにまずはコメントアウトしながらでもビルドを通すことを目標にすると良いと思います。

少しでもこれからSwift 3対応される方の助けになれば幸いです。

© Toreta, Inc.

Powered by Hatena Blog