Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

「After iOSDC」参加レポート

初めまして!
4月より Sansan に iOS アプリエンジニアとして入社しました髙橋佑一朗と申します。

先日行われた iOSDC Japan 2019 をテーマに、ZOZOテクノロジーズさん、JapanTaxiさん、Sansan の3社合同で開催した勉強会「After iOSDC」に参加したので、イベントの様子をお伝えしたいと思います。

zozotech-inc.connpass.com

LT

Sansan 株式会社 中川 「Sansan アプリで学ぶ iOS13 対応」

speakerdeck.com

トップバッターは弊社 iOS アプリエンジニアの中川で 最近 Hot (?)な話題であろうアプリの iOS13 対応についてのお話でした。 LT一発目ということもありまずは乾杯から!

なんで iOS13 対応するのか?

そこに新しい iOS があるから

毎年恒例の最新の iOS への対応ですね。
今年は Apple Developer のサイトに以下のように書いておりリミットが決まってしまっているので(来年の4月までまだ猶予はありますが)少し急ぎ目で対応しないといけない様子です💭

Starting April 2020, all new app updates will neet to be built with the iOS 13 SDK and support the all-screen design of iPhone Xs Max or later.
(2020年4月以降、新規Appと、AppのアップデートのすべてをiOS 13 SDKでビルドし、iPhone XS Max以降のオールスクリーンデザインをサポートする必要があります。)

(以前からこういうのありましたっけ...?💭)

Build が通らない

こちらも例年通りまずはビルドを通す作業ですね。

Swift5.0 でコンパイルしたモジュールが import できない

Sansan ではライブラリ管理に Carthage を使用していますが今までは Swift5.0 でコンパイルしたモジュールを使用していたため今回 Swift のバージョンが上がったことでエラーになっていました。なので Xcode11 で再度ライブラリ達をコンパイルし直してあげる必要があります。

一部のライブラリが Swift5.1 でビルドできない

これもあるあるだと思います。

今回の対応では XLActionController というライブラリでエラーが発生していましたが幸いにも master ブランチに Swift 5.1 への対応が取り込まれていたため Cartfile をバージョン指定でなくブランチ指定に切り替えることでライブラリのビルドができました。

アプリを iOS SDK 13 で動かしてみる

無事ビルドが通ったので早速実行を掛けるも・・・

f:id:chaaaaanu:20191001161712p:plain
画面が真っ黒に・・・

(これが DarkMode...?)

今回は RealmException が発生してクラッシュしているようでした。

Realm のプライマリーキープロパティがオブジェクトに存在しない

Swift 5.1 から明示的に String 型のインスタンス変数名が設定されなくなったために Primary Key property をString 型で定義していた場合にパースできず例外が発生していたそうです。(なるほど。わからん。)
こちらも幸いにも Realm 3.18.0 にて Swift 5.1 への対応がなされていたため Realm をアップデートすることで解消されました。

Sansan アプリを iOS 13 上で実行出来るようになった後に起こった問題。

Modal on Modal で NavigationBar の初回表示がおかしくなる

モーダルからモーダルを呼び出すと初回表示時のみ NavigationBar の下の部分が一部切り取られたような状態になってしまい NavigationBar と View の間に隙間ができてしまう問題。

原因はよくわかっていないようですが UINavigationBar の高さは 56pt で
初回表示時のみ内部の UIBarBackground や UINavigationBarContentView の高さが 44pt となってしまっており、その差分である 12pt 分が何も表示されない状態となってしまい隙間ができてしまっている状態でした。

NavigationBar の tintColor を background に設定してみたり色々試してみたようですが解決には至らず、カスタムで NavigationBar を用意すれば解決はできそうでしたが個別の OS 対応のみなのでそこまではできず、最終的にはセミモーダルを一旦やめるというようになりました。

modalPresentationStyle に fullScreen を設定することで従来の挙動のモーダルにすることができます。

ログイン時の遷移アニメーションがおかしい

セミモーダル上で Push 遷移をさせた時にアニメーションがうまく適用されない問題のようです。 遷移した後の画面が表示されるまでの一瞬ですが前の画面のコンテンツが残ってしまっていて少々不恰好に見えます。
解決策としては

  • 違和感のないカスタムアニメーションを作成する
  • セミモーダルをやめる
  • Push 遷移のアニメーションを無効にし、セミモーダルの非表示アニメーションを活用する

の三つが提案されており、最初の方法は少々コストがかかるということで見送りになりましたが、そのほかの

  • セミモーダルをやめて従来通りのモーダルの表示にする
  • Push 遷移のアニメーションを無効にし、セミモーダルの非表示アニメーションを活用する

の方法はどちらもお手軽で違和感なく次の画面までアニメーションでつなぐことができていました。

UINavigationBar.titleView に UISearchBar を設定した場合に制約が効かなくなった

Sansan では iOS11 から UISearchBar の高さが 56pt になってしまった事によるレイアウト崩れを防ぐために UISearchBar に height の制約を設定して高さを 44pt にして使っていましたが、 UISearchBar に設定されている intrinsicContentSize と AutoLayout の制約の優先度が iOS13 から変わってしまい、intrinsicContentSize が開発者が設定した AutoLayout の制約より優先されるようになってしまったため 56pt の高さになってしまい、レイアウトが崩れていました。

今回は以下の手順を踏んで実装する事で解決しました。

  1. intrinsicContentSize が設定されていない UIView で UISearchBar をラップ
  2. UISearchBar の translatesAutoresizingMaskIntoConstraints を false に
  3. UINavigationBar.titleView にラップした UIView をセット
  4. viewDidLayoutSubviews で制約を設定

😇😇😇しんどい😇😇😇 (本人談)

まとめ

ビルド前、実行前の対応は以前のようにライブラリのアップデートなど大幅に対応が変わることはなかったようですが、 iOS13 ではモーダルに大きな変更が入ったためモーダル周辺での不具合が多いように感じました。 モーダル遷移を行なっている箇所や UINavigationBar をがっつり使っている箇所はよくチェックした方が良さそうですね 👀

JapanTaxi 今入さん 「Unit Test with SwiftyMocky」

iOSDC ブース出展

今回のイベントが iOSDC 後の振り返りという趣旨もあって冒頭では iOSDC に出展したブースのお話がありました。

ブースでは JapanTaxi のサービスのデモが行われていたり、 「JapanTaxi」アプリで使われているライブラリやアーキテクチャなど技術のスタックペーパーを配布していたようです。

サービスを実際に触ることができたり、どんな技術を使って開発しているかがわかると少し開発の色などがわかって面白いなぁと感じました。

SwiftyMocky のご紹介

ここから本題のモックライブラリである SwiftyMocky のお話になります。

github.com

ざっくりですが特徴は

  • Protocol をモック化できるライブラリ
  • 大まかに Given, Verify, Perform の三つの機能を持っている
  • Sourcery の AutoMockable でモックファイルを作成する

といった感じでしょうか。

Protocol からモックを作成するということでProtocol指向なプロジェクトとは非常に相性が良さそうですね。

また Sourcery を使ってモックを自動生成できるところもテストにかける手間が少なくなりそうで嬉しいです。

モック作成の手順

1. モックにしたい Protocol を AutoMockable に準拠させる

まずはモック化したい Protocol を SwiftyMocky の提供する AutoMockable に準拠させて行きます。 この手順は Protocol を AutoMockable に準拠させる代わりに //sourcery: AutoMockable といったアノテーションを記述する事で代替可能です。 (Swift でアノテーションを使うのは珍しい気がします💭)

2. swiftymockey setup コマンドで Mockfile を生成

次に setup コマンドで Mockfile を生成します。
生成された Mockfile には どのプロジェクトを対象にするか、どのフレームワークを import するか、次のステップで生成される Mock.generated.swift をどこに生成するかなどが記述されています。

3. swiftymockey generate コマンドでモックを生成

generate コマンドでモックが作成されます!
Mock.generated.swift というファイルにひとまとめになっているようです。

困った時は?

導入がうまくいかなくても swiftymockey doctor コマンドを叩く事で問題がある箇所を教えてもらえるようです。
スライドでは例として生成した Mock.geenerate.swift が Test プロジェクトの Target Membership に設定されていない場合のエラーが紹介されていました。
ツールによっては導入でつまづいて思わぬところで時間を取ってしまうこともありますがこうしてライブラリ側でサポートをしてくれるのは非常にありがたいですね。

モックを使う

生成された Mock 達は ProtocolName + Mock といった名前になっているようです。

Given メソッド

モックに対して情報を与えるためのメソッドで、プロパティに値をセットしたり、メソッドに対しては戻り値をセットすることができるようです。

Verify メソッド

モックの動作を確認できるメソッドで、特定のプロパティやメソッドが何回呼ばれたかをテストすることができます。
第二引数の count には 回数をセットするようになっていますが、2や3などの具体的な数字だけでなく .never.more(than:), .custom({ count → Bool in ... }) といったenumをセットする事もでき、柔軟な記述が可能となっているようです。 そのほかにも Verify メソッドでは引数に何が渡ってきたかもチェックすることができます。
どういったロジックに適用できそうかイメージはまだできていませんがテストの表現力も上がりそうだなぁと感じていました。(小並感

Perform メソッド

モックのメソッドが呼ばれた後の処理を設定するためのメソッドで、クロージャを引数にとるメソッドをモック化した際に Perform を使うことでクロージャの操作が可能になります。 completion を設定しているメソッドなどのテストが簡単になりそうですね。

まとめ

  • Protocol を簡単にモックにすることができる
  • Protocol を多用したプロジェクトに向いている
  • メソッドの呼ばれた回数の検査でテストの幅も広がる
  • モックを簡単に書けるのでテストを書く気になる

まとめとして最後に挙げられていた モックを簡単に書けるのでテストを書く気になる というのは非常に重要だなと思いました。
僕もまだテストコードを書く習慣が付いていないのであまり書く気が起こらない事もありますが SwiftyMocky を使う事でテストへのモチベーションを高めて行けたらと思います!

ZOZOテクノロジーズ 林さん 「ZOZOTOWNアプリにスナップショットテストを導入してみました」

スナップショットテストをなぜ導入の背景

なぜスナップショットテストを導入したかについて ZOZOTOWN アプリの開発の課題から入っていきました。

開発全体の課題として

  • 多くの拠点で開発とその確認作業が行われている (青山、幕張、リモート)
  • コードレビュー後のデザインのデグレードの防止策がない

の2点が挙げられていました。

今回は ZOZOTOWN アプリにある 類似アイテム検索画面 を例にとって、以下のようなスナップショットテストを導入する事で解決したい課題についてお話しされていました。

デザイン確認の課題

今回のスナップショットテストに関わりそうな課題としては

  • 細部までこだわっているため細かな調整が発生する
  • 早い段階で画面のチェックが入るのでモックデータが必要
  • 修正ごとの画面キャプチャの手間がかかる
  • 直感的に伝えづらい
  • 複数端末の確認が必要

が挙げられていました。

iOS 端末も色々な画面サイズの端末が出てきていて全ての端末を網羅するのはなかなか難しくなってきていると思います。またデザインの調整も拠点が離れているとコミニュケーションも非同期的になりやすいので直接やりとりするより手間がかかってしまいますね。

デグレーションの課題

アプリのデグレーションについては

  • 全ての対応端末を確認していない
  • UI の実装を修正した後にデザインチェック済みの画面との比較ができない

などの課題が挙げられていました。

スナップショットテストの導入

一通り課題と期待値をお話して頂いたところでいよいよ導入の部分に入っていきます。

テスト方針

お話されていたテスト方針は次の二つです。

  • 元のコードを壊したくない
  • 全ての対応端末を自動でテスト

UI周りはテストコードを書くのが非常に難しい(書けてもあまり書く意味がない)ので、壊れていないかどうか確認できると画面周りを触る時のストレスがかなり減りそう...。後者は iPhone も多様な端末が出てきているため実現できると大きく手間を削減することができそうです...!

1. ios-snapshot-test-case の追加

github.com

スライドでは ios-snapshot-test-case というライブラリを使用してスナップショットテストを行なっていました。他のライブラリと同じように Carthage からインストールすることができます。また、撮影したスナップショットの保存場所についても EditScheme → Environments から設定可能です。

2. OHHTTPStubs

github.com

デザインの確認の課題で上がっていたモックの問題を解消するためのライブラリです。ローカルに json ファイルを置いておくだけでHTTPレスポンスをモックして画面表示ができるため非常に便利です。他にも有名なツールとして Charles の名前も上がっていました(弊社Sansanでは Charles を使用しています)。

3. productNameSnapShotTestsを追加

Target に zozoSnapShotTests と言う Target を追加して、1, 2 の手順でインストールした二つのフレームワークを追加し、SnapshotTest の初期設定を行います。初期設定は FBSnapShotTestCase に定義されている setup メソッドをオーバーライドして行うようです。

  • recordMode
    • true: 参考画像を生成
    • false: 既に撮影した参考画像との比較をする
  • folderName
    • 画像を保存するフォルダ名
  • fileNameOptions
    • 参考画像名
    • .device, .OS, .screenSize などいろいろ指定できる

SnapShotTest を実行するには、大まかに

  1. 上記の初期設定を行う
  2. FBSnapShotTestCase を継承したクラスを作成
  3. 適当なメソッドで ViewController を生成して FBSnapShotVerifyView に ViewController.view をセット
  4. 実行!

と言った感じで非常にお手軽ですね。
また、撮影した画面に差分が存在した場合は FailureDiffs というフォルダに差分画像を出力してくれるようです。さらに Fastlane と組み合わせることで必要な端末全てに対してスナップショットテストを走らせることもできるようです。

スナップショットテスト時に発生した問題

原因はわかっていないようですが、スナップショットテストではタイミングによっては コレクションビューの画像が非同期に取得できず真っ白になってしまうことがあったようでした。解決策として テストしたい ViewController を UIWindow に貼ってテストすることで解消したようです。

スナップショットテスト導入の結果と課題

導入後のメリットとして

  • 画面キャプチャが楽になった
  • 差分が見れるのでデグレの不安から解放された
  • スナップショットでも全ての対応端末を確認できる

と言った点が挙げられていました。 冒頭で挙げられていた課題についてはほとんど解消されているように思いますね!

導入後の課題としては

  • 表示状態を変更したい場合は実装を再設計する必要がある
  • アニメーションの確認ができない

スナップショットなのでどうしても動くものの確認はしづらいですね。。。 (アニメーションまで確認できるテストツールがあるのだろうか・・・)

まとめ

iOS端末も様々なデバイスが出てきていて、全ての端末でレイアウト崩れがないかなどのテストは非常にコストがかかるように思います。スクリーンショトなどを撮ってチェックしてもらうような場合は尚更ですが、コストを抑えつつ幅広い端末のUIテストを可能にしてくれるスナップショットテストはすごくイケてるテストだと思いました!
Sansan でもぜひ取り入れていきたいです!(誰か対応してくれないかな...)

LTまとめ

どの発表も非常に有用な物ばかりでとても参考になりました!
参加していた方々も発表の所々で「おー」と歓声を上げていたり笑っていたりして反応がとてもよく、会場の雰囲気は和気藹々としていて良かったと思います! お酒が入っていたのもあるのでしょうか?

パネルディスカッション

LTのあとはパネルディスカッションへと移っていきました! パネラーとして、JapanTaxi 玉澤さん、ZOZO テクノロジーズ 伴潤さん、弊社 Sansan 坂本の三名が、そしてコーディネーターとして JapanTaxi 日浅さんを迎えてのディスカッションとなりました。

以下にパネルディスカッションの質問と各パネラーさんのお答えの内容を大まかにまとめます!

最初はやっぱり・・・

第二部ということで最初はやはり乾杯から始まりました。アイスブレイクは大事ですね(既にブレイクされている気がしましたが)。ちなみに僕は下戸です。

f:id:chaaaaanu:20191001141440j:plain
まずは乾杯!

今年のiOSDCどうでしたか?

iOSDC の振り返りイベントということで導入として一発目はこの質問でした。

伴さん(ZOZOテクノロジーズ)

iOSDC は4度目の参加。今年は 2.5日 だったのであっという間に感じた。
トークは時間が伸びた分、数は減っているかと思うが、選ぶのが大変なほどトラックがたくさんあって充実していた。

玉澤さん(JapanTaxi)

トークの時間が伸びたため、完全理解系のような踏み込んだ発表が多いように感じた。
LLVM のような低レイヤーの話や、一般的な iOS の設計の話や iOS13 関連の話だったり、Push通知などの UI よりも上の部分の話も取り上げられていて、セッションのバランスも良いように感じた。
スナップショットや AWS Device farm などの自動化の技術が興味深かった。

坂本(Sansan)

いろいろなセッションがあって面白かった。
iOSDC は、セッションを聞いて学ぶことがメインのカンファレンスと比べてお祭り感があるところがいい。久々に会う人たちともたのしく話せる雰囲気がある。

ブースでとったアンケート結果発表

弊社 Sansan と ZOZOテクノロジーズさんがそれぞれブースで参加者にアンケートをとっていたので、その結果についてのディスカッションですね!
シールを貼って回答するタイプのアンケートだったので、その画像を結果として発表しました!

f:id:chaaaaanu:20191001142634p:plain
ZOZOテクノロジーズアンケート
f:id:chaaaaanu:20191001141721p:plain
Sansan のアンケート

アプリのレイアウトについて(ZOZOテクノロジーズのアンケート)

坂本(Sansan)

Sansan では静的な部分は IB で、動的な部分はコードで書いている。 IB などのリソースへのアクセスには SwiftGen を使っているのでエラーがあればコンパイル時にわかる。 Storyboard などの Identifier も SwiftGen で管理しているので安全にアクセスできる。
Segue もいけるけどあまり使ってない。

玉澤さん(JapanTaxi)

ライブラリの製作者がメンテナンスしてくれているのは前提だけど、型安全、 null 安全、 typo を担保することができるのはとても良いと思う。
JapanTaxi は新しい機能については全部コードで書いている。
古いものは IB 使っている。
SwiftUI などコードでのレイアウトに流れが来ているように感じる。

伴さん(ZOZOテクノロジーズ)

レイアウトの書き方が気になるのでアンケートにしました。
ZOZO は基本 IB 定義しきれないものはコードで書く王道パターン。アンケートの図で言うと右寄り。
その辺りに集中するかと思いきや半々になっていて驚いた。
ちなみに個人的には全部コード派に属している。アンケートを見ると全部コード派が一定数いる。
コードで書きたいけど一部 IB というの人はあまりいないようだ。

サポートOSについて(ZOZOテクノロジーズのアンケート)

玉澤さん(JapanTaxi)

iOS8 サポートしているのにとても驚いた。
事業的に OS を切るメリットよりも。OS を残しておくことでユーザを確保できるということの方が重要。

---OS をきる判断はどうしている?

JapanTaxi の場合は MAU などの基準はあるが、それは開発部が持っている基準の話なのでそれをベースに上長やマーケティングチームと交渉する。機械的なルールは設けていない。サポートを切っても使えないことはないので「こういう施策をいれてから切りたい」みたいな話は結構ある。

伴さん(ZOZOテクノロジーズ)

---OS をきる判断はどうしている?

まず、アンケートに iOS8 が入っている理由がある。それは、ZOZOTOWN が先月まで iOS8 もサポート(!) していたから。
現在は iOS10 までのサポート。(iOS9 はない)
ユーザのパーセンテージと独自の基準を元に判断している。
iOS10 からのサポートに iOS9 から入っているクラスで結構使いたいものがあったので急に快適になった。

坂本(Ssnsan)

---OS をきる判断はどうしている?

明確にルールとして決まっているわけではないが、DAU が一定以下になったら切っている。
この間 iOS10 のサポートを切って今は iOS11 以降のサポート。iOS12 まで切れれば SwiftUI が使える・・・!

普段使っているキーボードについて(Sansanのアンケート)

坂本(Sansan)

個人的にはタイプした時のスコスコ感があるキーボードはすごく好き。
詳しくはキーボードずきな中川に譲る。

中川(Sansan)

アンケートでは1票しか入っていない Craw44 というキーボードを使っている。
アンケートに選択肢として出しているのは Sansan のモバイルエンジニアが使っているキーボード。世間のばらけ方とは違った。
標準のキーボードを使っている人がかなり多いという印象。

クロスプラットフォームについて(Sansanのアンケート)

---使ったことない人も一定数いるけど大体何かしら使ったことあるようですね。

坂本(Sansan)

Sansan で使っているわけではないが世の中でどれくらい使われていたのか気になっている。

---Sansanでの採用予定はあるか?

今からはなかなか厳しい。新しい開発などがあれば Flutter などが候補に上がってくる可能性はある。

---Flutter や SwfitUI のような宣言的なUIの書き方はやはり Flutter が出てから浸透してきた?

React も Redux, Flux など影響を与えているように感じる。

玉澤さん(JapanTaxi)

---クロスプラットフォームやりたいですね。

メインアプリで使うのはしんどいけど、実証実験アプリを単発で開発する機会があるのでそこで使うのはアリかもしれない。

Xcode11, iOS 13 対応した?

坂本(Sansan)

私は Eight という個人向け名刺アプリの担当で、Eightについていうと Xcode11 の対応はまだ出来ていない。
iOS13 の方は BLE の許諾が出る問題と、スキャナの SSID を取得するのに位置情報のパーミッションが必要になる問題は対応、リリース済み。
パーミッション関係がだんだん厳しくなってきていて少し大変。

伴さん(ZOZOテクノロジーズ)

ZOZOTOWN はまだこれからという感じ。さっきの中川さんの LT でいうと Build を通すところまでは終わっている。
動かすと色々なところで問題があった。歴史あるアプリではよくあると思うが Key-Value coding が iOS の API に対して行われているとか。
対応したい気持ちはある。

玉澤さん(JapanTaxi)

年内くらいには対応できるかなという感じ。
Xcode 10.3 でビルドされた現行アプリが iOS13 で動くかどうかはテスト済みで、特に問題なし。
ダークモードなどを一緒にやるとなるとデザイナーさんと一緒に動いていかないといけない。
スナップショットテストを使えると確認の手間がなくなりそう。
JapanTaxi では地図の色味を全てデザイナが定義して決めている(一般道路や高速道路の色など) 。 そこもダークモード対応しないといけないのデザイナさんは苦労しそう。

SwiftUI, Combine をアプリに組み込んだ?

玉澤さん(JapanTaxi)

RIBsが Rx 依存のアーキテクチャなので、RxSwift を使っている。 バックポートライブラリなどが出てくれば SwiftUI などを使うのはあり。
Combine は RxCombine のようなものはあるが、それを使うよりは元からCombine 使った方がパフォーマンスも良い。
本格的に使えるのか探り探りですがキャッチアップはしておきたい。

伴さん(ZOZOテクノロジーズ)

使っていない。 個人的な意見として、思想は SwiftUI, Combine 共にとても好き。
コードレイアウトが好きなので、弱点であるレイアウトの想像のし辛さをわかりやすいアプローチでカバーしてくれているところがいい。
ツールとしての力が上がることに期待している。

坂本(Sansan)

iOS12 を切れれば是非取り入れたい。
今の時点で SwiftUI で凝ったレイアウトをしようとすると死ぬのかなという感じはある。
Eight は Rxを使った MVVM で動いている。
Combine と RxSwift のブリッジは多分皆ハマるのでなんとかなると思っている。
SwiftUI の State の管理などと Combine がどう合わさるのかといったところは整理しておく必要がありそう。
Android の xml は読めるけど StoryBoard 読めない。
あるべき形だとは思っているので長期的には SwiftUI に寄せていきたい。

最後に

iOSDCが終わってから 1ヶ月も経っていませんが、各社が iOSDC で得た学びを早速取り入れて実務に生かしていました。 パネルディスカッションでは各社のレイアウト事情などアプリを見るだけどではわからない話や、クロスプラットフォームについての姿勢、SwiftUI への期待など非常に気になっているところが多く聞けて、参加者のみなさんにとっても興味深いイベントになったのではないかと思います!

© Sansan, Inc.