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

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対応される方の助けになれば幸いです。

トレタのRailsを4.2にアップグレードしました

サーバサイドエンジニアの中村です。 今回はトレタのAPIに使っているRailsのバージョンを4.1.12から4.2.7.1にアップグレードしたので、その手順について紹介いたします。

トレタのAPIでrake statsを実行した結果は以下の通りです。同じ規模感のサービスのRailsをアップグレードするときに役立てていただけたら幸いです。

+----------------------+-------+-------+---------+---------+-----+-------+
| Name                 | Lines |   LOC | Classes | Methods | M/C | LOC/M |
+----------------------+-------+-------+---------+---------+-----+-------+
| Controllers          | 19554 | 16806 |     165 |     831 |   5 |    18 |
| Helpers              |   267 |   244 |       0 |      24 |   0 |     8 |
| Models               | 10685 |  6564 |     161 |     435 |   2 |    13 |
| Mailers              |   345 |   295 |      10 |      17 |   1 |    15 |
| Javascripts          | 14813 | 10032 |      16 |    1270 |  79 |     5 |
| Libraries            |  1316 |   992 |      24 |      89 |   3 |     9 |
| Concern specs        |   478 |   396 |       0 |       3 |   0 |   130 |
| Controller specs     | 23935 | 21178 |       1 |       3 |   3 |  7057 |
| Helper specs         |   147 |   118 |       0 |       0 |   0 |     0 |
| Lib specs            |   416 |   375 |       0 |       0 |   0 |     0 |
| Mailer specs         |  1958 |  1678 |       0 |       1 |   0 |  1676 |
| Model specs          | 11251 |  8762 |       0 |       2 |   0 |  4379 |
| Request specs        |    91 |    66 |       0 |       0 |   0 |     0 |
| View specs           |   679 |   574 |       0 |       0 |   0 |     0 |
| Worker specs         |  2906 |  2530 |       1 |       3 |   3 |   841 |
+----------------------+-------+-------+---------+---------+-----+-------+
| Total                | 88841 | 70610 |     378 |    2678 |   7 |    24 |
+----------------------+-------+-------+---------+---------+-----+-------+
  Code LOC: 34933     Test LOC: 35677     Code to Test Ratio: 1:1.0

サーバサイドチームのルールとして、APIの機能開発を行うときは必ずRSpecでのテストを書くようにしているので、テストが存在しないclassはほぼありません。現在、テストケースの数は約3000ケースあります。

Railsアップグレードの手順

手順は以下のように行いました。

  1. railsのバージョンをアップグレードする
  2. その他に依存するgemをbundle updateでアップグレードする
  3. rake rails:updateを実行する
  4. テストを全て通す
  5. staging環境にデプロイし、QAテストを行う
  6. 本番環境にデプロイ

非互換な変更や修正するべき部分の確認はRailsアップグレードガイドを参考にしました。

gemをbundle updateでアップグレードする

rails本体のバージョンをアップグレード後、テストを実行します。 この時点で1/3ぐらいのテストが落ちていたので、落ちたテストのstacktraceを確認して、関連しそうなgemをbundle updateで1つ1つアップグレードします。

ここで陥った問題として、csso-railsのバージョンを上げすぎてしまい、therubyracerで読み込めないJavaScript Codeが吐かれてしまいrake assets:precompileが失敗してしまいました。

この問題はcsso-railsのバージョンをダウングレードして回避するという方法を取りました。他の方法としては、therubyracermini_racerやNode.jsに置き換えると方法が考えられます。

関連するgemを全てアップグレードし終えたら、落ちるテストが10件ほどになったので、残りは1つ1つ地道に修正しました。この時点で、全てのテストが通りました。

QAテスト

staging環境にRailsアップグレード用ブランチをデプロイし、QAテストを行います。 テスト期間を2週間設けて、QAエンジニアとアプリ開発チームの協力の元、念入りにQAテストを行いました。

本番環境にデプロイ

通常のAPIのデプロイは週1回、平日昼間に行っていますが、今回のアップグレードはトレタを利用していただいている店舗さまへの影響を最小限にするために、深夜にデプロイを行いました。

EC2インスタンスの数を通常の倍に増やし、半数はアップグレードブランチのデプロイがされないようにしておき、万が一のためにすぐ切り戻せるようにしておきます。

デプロイ時、一部のテストが実行時間依存になっていることが原因でテストが落ちたことや、EC2インスタンスのネットワークが不調でデプロイ失敗など波乱がありましたが、無事デプロイ完了しました。

半日ほど様子を見て、本番で問題が起きていないことを確認後、古いコードが乗っているEC2インスタンスを削除し、全てのサーバーが4.2系のコードに切り替わりアップグレード作業が完了しました。

特にお客様からのお問い合わせも無く、安心してお店でトレタを使っていただいています。

まとめ

トレタのAPIのRailsのバージョンを1年半ぶりにアップグレードしました。 トレタのエンジニアはまだ人数も少ないこともありますが、基本的に機能開発をメインとするエンジニアしかおらず、リファクタリングや開発基盤構築をメインの職責とするエンジニアはいません。

しかし、継続的にコードを改善していく文化が根付いており、日頃から小さい改善を積み重ねていくことで、コードが大きな負債にならないように日々努力しています。今回のアップグレード作業でもほぼ問題なく、スムーズにアップグレードすることができました。

トレタでは機能開発が大好きで、継続的なコードの改善をやりたいというサーバサイドエンジニアを募集しています。トレタに興味を持たれた方はぜひこちらからご応募お願いします!

最速かつ継続的に価値を届け続けるためのユーザー理解

こんにちは。デザイナーの上ノ郷谷です。私たちトレタが提供している予約/顧客管理サービスは、ユーザーである飲食店のスタッフからみると会社が選んだ業務ツールです。しかも予約や顧客を管理することは飲食店にとって大切とはいえ、メインの業務ではありません。そういった現場で使ってもらい、サービスの価値を上げていくには、いつも通りに使っていたら、気付かないうちに便利になっていると感じてもらえるような、できるだけ早く、継続的に価値を提供していく必要があると考えています。使うための学習や、操作中の迷い、没入させてしまったりすることがすべてコストになってしまうからです。

そのために私たちは、根源的なユーザー理解と、開発チーム内での認識にブレがないように情報フォーマットを持って、コミュニケーションコストを下げるなど、さまざまな取り組みを行なっています。今回はその中でも根源的なユーザー理解のために行なっているいくつかの取り組みについて書いてみます。

要望レビュー会

トレタではセールスチームやサポートチームがユーザーから聞いた要望を投稿するフォームが用意されています。ここで投稿された内容はスプレッドシートに蓄積されるだけでなく、社内コミュニケーションツールとして使っている Slack の専用チャンネルにも流れます。この要望をデザイナーとセールスチームのメンバーとで毎週レビューする会を行っています。

この会ではどういった店舗からの声なのかだけではなく、手段の提案に近い要望の背景にある課題や、プロダクト上で何が問題になっているかの現状把握、運用面での工夫を含む、解決プランの提案、検討中の仕様共有も行います。デザイナーはこのヒアリング会の中で手段の提案になりがちな要望から課題を抽出することを意識しています。

要望レビュー会は、ユーザーの理解以外にも、セールスチームのメンバーと製品開発メンバーの距離を縮めて保つことも目的のひとつです。

店舗ヒアリング

要望レビュー会のなかで、その要望がどんな店舗からあがっているのか、店舗の立地や規模など課題を明確にするために知るべき事があったり、直接話を聞いてみたいと思った場合はすぐに担当のセールスメンバーに声をかけて、訪問させていただくようにしています。店舗でのヒアリングはデザイナーだけではなくエンジニアも行くことがあります。

店舗でのヒアリングは、基本的に営業時間中ではなく、開店前の準備中だったりします。貴重な時間のなかで、しっかり話も伺いたいので聞きたい内容は事前に準備します。ですが、実際は雑談形式になる事が多いです。事前に用意した「聞くこと」についてももちろん尋ねますが、できるだけ業務中に近い内容を話してもらうためだったり、雑談の方が普段抱えている課題を引き出しやすいのと、ある点に集中して質問して答えを求めないほうが、状況や関連している原因に気付くことが多いからです。そのため私は、ヒアリング時はノートではなくカードに、話を聞いていて気になったキーワードを書くようにしています (私は付箋だと書くときにあつかいにくいので、メッセージカードを使っています)。

f:id:NiPeke:20161219001222j:plain

会社に戻ったらキーワードを書いたカードを机の上に、事前にまとめていた聞きたかったことを基準にマッピングしていきます。そうすると雑談で前後したりしていた話の筋がすうっと通り、業務時間外のインタビューでは知ることが難しかった現場の声が見えてきます。このマッピングしたカードと、要点や所感をまとめたものをすぐに社内情報共有ツールを通して、メンバーに共有しています。

導入店さまには営業中にも客としてもお邪魔します。基本的に食事を楽しむためですが、どういったタイミングで何をするためにどれくらいの時間 iPad に触れているかを見たりしています。余談ですが、会社からも導入店さまの店に行くことを推奨されていて、それを補助する福利厚生もあります。

さいごに

ほかにも根源的なユーザー理解のための取り組みはありますが、特徴的なものを挙げてみました。私たちはサービスの価値を上げるために、サービス提供者という立場にいながらも、できるだけユーザーと同じ視点でサービスに触れる必要があります。その視点から課題を明確にし、仮説検証、改善プランのレビューを重ね、提供する価値の精度を高めるというイテレーションを回し続けることで、最速かつ継続的に価値を届け続けるプロセス、組織を作っていっています。


この記事はトレタ Advent Calendar 2016の記事です。はインフラエンジニア津田の「明日からできる日常の自動化」でした。は QA エンジニア井上の「Todoist で始めるタスク管理」です。

© Toreta, Inc.

Powered by Hatena Blog