クックパッド開発者ブログスマートフォン用の表示で見るクックパッド開発者ブログ2019-08-16UICollectionViewでページングスクロールを実装するiOSこんにちは。新規サービス開発部の中村です。普段は「たべドリ」アプリの開発をしています。「たべドリ」は料理の学習アプリです。詳細はこちらの記事をご覧ください。ページングスクロールを実装する方法について解説します。左右のコンテンツが少し見えているカルーセルUIで、以下の要件を満たすものです。先頭にヘッダーを表示するセルが水平方向にページングスクロールする色々な実装方法があると思いますが、今回はヘッダーがあるため複数の異なる幅のViewを表示させながら、ページングスクロールを実現する方法を解説します。実装のポイントは以下の2点です。UICollectionViewFlowLayoutのサブクラスを作成しtargetContentOffset(forProposedContentOffset:withScrollingVelocity:)メソッドをオーバーライドしてUICollectionViewのcontentOffsetを計算するUICollectionViewのdecelerationRateプロパティに.fastを指定する以降、実装方法の詳細を解説していきます。ベースとなる画面の作成まずベースとなる画面を作成します。UICollectionViewFlowLayoutを使いヘッダーとセルを表示します。UICollectionViewFlowLayoutのscrollDirectionプロパティは.horizontalを指定し横スクロールさせます。ヘッダーとセルのサイズ、セクションとセルのマージンは任意の値を指定します。省略)ここまではUICollectionViewControllerの基本的な実装です。プロパティページングスクロールさせたい場合、最初に試したくなるのがUIScrollViewのisPagingEnabledプロパティをtrueに指定することですが、この方法ではセルが画面の中途半端な位置に表示されてしまいます。これはcollectionViewの横幅の単位でスクロールされるためです。この方法でもセルの幅をcollectionViewの横幅と同じ値に設定し、セルのマージンを0に指定することで画面中央にセルを表示させることが可能です。今回はセルの幅と異なる幅のヘッダーも表示させる必要があるためこの方法では実現できません。セルを画面中央に表示するそこでUICollectionViewFlowLayoutのサブクラスを作成しtargetContentOffset(forProposedContentOffset:withScrollingVelocity:)メソッドをオーバーライドします。このメソッドはユーザーが画面をスクロールして指を離した後に呼ばれます。メソッドの戻り値はスクロールアニメーションが終わった後のcollectionViewのcontentOffsetの値となります。このメソッドを以下のように実装します。(省略)}velocity引数をもとにユーザーが左右どちらにスワイプしたか、またはスワイプせずに指を離したかの判定をしています。スワイプしていない場合は画面中央に近いUICollectionViewLyaoutAttributesをもとに座標を計算します。ユーザーが左にスワイプした場合は取得したUICollectionViewLayoutAttributes配列の最後の要素、右スワイプの場合は最初の要素をもとに座標を計算します。これでセルを画面中央に表示できます。セルの位置は期待通りになりましたが、スクロールの速度が緩やかなのでスナップが効いた動きにします。UIScrollViewのdecelerationRateプロパティを.fastに指定するとスクロールの減速が通常より速くなりスナップの効いた動作となります。.fast1ページずつのページングこれで完成したように見えますが、スクロールの仕方によっては1ページずつではなくページを飛ばしてスクロールしてしまうことがあります。これを防ぎたい場合targetContentOffset(forProposedContentOffset:withScrollingVelocity:)メソッドで取得しているUICollectionViewLayoutAttributes配列の取得タイミングを、スクロールする直前に変更し、それをもとに座標を計算することで解決できます。以下のように実装を追加・変更します。(省略)}UIScrollViewDelegateのscrollViewWillBeginDragging(_:)が呼ばれたタイミングでprepareForPaging()メソッドを呼びます。このメソッドでスクロール直前のUICollectionViewLayoutAttributes配列をlayoutAttributesForPagingプロパティに保存しておき、targetContentOffset(forProposedContentOffset:withScrollingVelocity:)メソッドの中で保存した配列をもとに座標を計算するように変更します。これで1ページずつページングできるようになりました。ページングスクロールを実装する方法を解説しました。このようなUIを実装することは稀だとは思いますが、何かの参考になれば幸いです。サンプルプロジェクトはこちらhttps://github.com/nkmrh/PagingCollectionViewです。2.0における消費型商品の決済の承認(acknowledgement)についてAndroidユーザ・決済基盤部の宇津(@uzzu)です。ユーザ・決済基盤部ではそれらのアプリの決済情報を取り扱う共通決済基盤サービスとクライアントライブラリを日々開発しています。#3のLT枠にてどのように対応していったか発表させて頂きました。speakerdeck.com本記事では同発表にて時間が足りず深堀りできなかった、消費型商品における決済の承認(acknowledgement)対応について解説します。スライドと合わせて読んで頂ければ幸いです。消費型商品における2.0とそれ以前との違い2.0以前の消費型商品の購入フローは概ね以下の図のようになっていたかと思います。2.0以前の購入フロー2.0からはこれに加えて、決済の承認が必要になります。Purchaseを除く)のですが、3日以内に開発者が決済の承認を行わない場合返金されます。通信断や障害等で購入フローが正常に完了せず商品が付与されなかったユーザが自動的に救済されるようになるのは、サポートコスト削減の面でも非常に良いですね。購入フローに処理が1ステップ追加されただけのように見えます。API.とあるように、アプリ上でconsumeAsyncを呼び出す事で消費(consume)しつつ決済の承認も行われるので、図に追加した5については特にやる事はないのでは?と思われた方もいるかと思います。この違いによってアプリ改ざんに対するリスクを考慮する必要性がでてきます。consumeAsyncを呼び出さないようにアプリを改ざんされる事を想定した場合、購入処理を実施すると以下のように処理されます。消費型商品の購入ボタンを押す購入フローに則り商品が付与されるconsumeAsyncを呼び出さない為消費が行われないが、商品は既に付与されている(決済が未承認の間、商品は消費されない為、再購入はできない)3日後、決済が未承認の為返金される返金されると消費型商品が再度購入可能になる1に戻るつまり、2.0以前の購入フローの実装のまま愚直に2.0対応してしまうと、アプリ改ざんによって3日毎に消費型商品を無料で取得する事ができてしまいます。acknowledgeが用意されています。クックパッドの共通決済基盤サービスではこれを利用して決済を承認しユーザと決済情報の紐付けが正常に行われた上で、各サービスで商品の付与ができる状態とするようにしています。商品付与後、アプリ上でconsumeAsyncします。この対策方法はアプリ改ざんに対するリスク、及び決済の承認に関連するアプリ上での購入フローの実装が2.0とそれ以前とで変わらないのが利点です。決済状態と商品付与状態の整合性の担保ができている前提での対策方法になると考えています。クックパッドの共通決済基盤サービスにおける整合性についての取り組みは以下の記事を参考にしてください。https://techlife.cookpad.com/entry/2016/06/01/070000加えて決済を承認するタイミングについて、商品を付与した上で決済を承認するか、あるいはレシート検証を終えた段階で一旦決済を承認した後に商品を付与するかを検討するかと思います。クックパッドの共通決済基盤サービスではどうしているかというと、消費型商品においては購入フローの完了処理である所の消費処理がアプリ上でしか実施できない為、購入処理の冪等性を担保できるよう後者を選択しています。Queueで非同期に決済の承認を実施しています。Libraryに決済の承認をするためのメソッド(BillingClient#acknowledgePurchase)が用意されています。承認済か否かを検証した上で商品を付与した後、アプリ上でconsumeAsyncするような購入フローにします。サーバ間通信で決済の承認状態を検証する為、対策A同様に介入される余地はないと考えています。その仕組みを整えるのはそれなりに開発コストがかかりそうです。決済処理フローはそのままにアプリ改ざん対策に本腰を入れていくとしても、アプリ改ざん対策はいたちごっこになってしまう為、運用コストの増大が予想されます。素直に前述の対策Aないし対策Bを適用するのが良さそうです。2.0における消費型商品の決済の承認(acknowledgement)について解説しました。2.0導入の手助けとなれば幸いです。コスト最適化を組織的に取り組んでいるため、今回はそれについてご紹介します。前半では、そもそもの話として「なぜコスト最適化が重要なのか」「何が難しいのか」「何をすべきなのか」といったことを述べます。トピックで、一度しっかり言語化しておいてもいいかなと考えたからです。内容のほとんどはわたしの脳内ダンプで、クックパッドという会社のコンテキストや組織としてのステージが前提になっているため、大多数の組織について当てはまる内容とは限りません。維持管理するためのフローと、それを支えるモニタリングシステムについて述べます。利用するいかなる組織においても、今日から使えるテクニックとなるでしょう。すると、似たような仕組みを整えている組織もあるかもしれません。パブリッククラウドのコスト最適化の重要性オンプレミスであれクラウドであれ、インフラにかかるコストを最適化し、できる限り無駄のない状態を保つことが重要なのは言うまでもないでしょう。クラウドでは、必要なときに必要な分だけお金を対価にリソースを得られる反面、簡単な操作でリソースを増やすことができるため、気づいたときには大きな無駄が発生していたという自体も起こりえます。組織において、お金という限られたリソースは大変重要かつ貴重なものです。投資やその他の重要な部分に回すことができます。インフラコストは投資と違って単に失われるだけなので、エンジニアリングリソースを割いて最適化することは、十分に理にかなっているでしょう。通してリソースを操作できるため、ソフトウェアエンジニアリングで解決できる部分が大きいです。コスト最適化に見て見ぬふりをしていると、じわじわと組織の首を絞めていきますし、節約できていれば本来有意義に使えたお金が失われることになります。キャッシュが枯渇してから慌てていては、もう手遅れです。つの軸が存在するとわたしは考えています。適切に保つ、スポットインスタンスを積極的に利用する、など。適切にプロビジョン・スケーリングしていて無駄遣いしていない状態に保つこと。AWSだと、以下のような取り組みがあげられます。公式ページにも書かれています。スポットインスタンスを積極的に利用する。購入によってある程度のキャパシティを予約・前払いし、オンデマンドインスタンスやマネージドサービスを割安で利用する。いったサービスを利用してオーバープロビジョニングなリソースを発見し、インスタンスサイズやその数を適切に保つようオペレーションする。番、すなわちリソースプールそのもののコスト最適化については非常に熟れており、理想に近い状態を維持することができています。番のリソースプールの利用については、これからも継続的に取り組んでいくべき課題となっています。クラスタを安定して運用できています。クラスタで動いています。これは、コストの最適化に大きく寄与しています。運用このトピックは、記事の後半で述べるためここではいったん隅に置いておきます。(ですよね?)、実際にはその道のりは非常に険しいです。開発チームとの《調整》が発生。そのアプリケーションを普段から触っていてドメイン知識のある開発チームが対応したほうが早い。限られた人的リソースですべてのインフラのリソースの状況をウォッチして対応し続けるのは組織の拡大に対してスケールしない。いった問題があります。いう概念が解決しようとしてる問題に密接に関連しています。本にも、コスト最適化のためのオペレーションはトイルとして扱われています。いう概念を絡めることには議論があるかもしれません。守れる範囲でコストを切り詰めていくことは信頼性に深く関わることだと信じています。方針とも合致しますね。取り組んでいる、開発チームへの権限と責任の移譲が進み、適切にコストをモニタリングできる仕組みが整えば解決できる見込みがあります。前の記事ですが、権限と責任の移譲については、クックパッドの開発基盤、インフラ環境での開発で心がけているラストワンマイルでも触れられています。各開発チームが、自分たちのサービスにかかっているインフラコストを把握できるようなり、自律的にリソースプールの利用を最適化できると、組織の拡大に対してスケールするようになるでしょう。開発チーム自身の手で実装起因の無駄を改善することができ、スケールダウンのための停止メンテナンスが必要になった場合でも、チーム内で調整を完結して行うことができます。リソースプールそのものの最適化と、リソースプールの利用をモニタリングできる仕組みを提供することに専念し、必要に応じて開発チームを手伝うことはありますが、基本的には開発チームにコスト最適化の責任と権限を持ってもらうのです。プロジェクトや部署ごとに、どの程度サービス運用のためのインフラコストがかかっているかを把握できるようなダッシュボードを作ることができると、財務管理上でもメリットになるでしょう。まるっと「インフラ代金」として予算管理しているものが、開発チームや部署、プロジェクトごとに予算を細かく設定し、追跡することができるのです。適切な運用によって、開発チームが利用するリソースプール自体のコスト最適化は限界に近づきつつあります。エンジニアリングで解決していく必要があります。維持管理のフローと、それを支えるモニタリングシステムについて説明します。費用削減効果を最大限に発揮させるためには、その状況の変化を察知して、以下のようなオペレーションによって理想の状態に戻るようにメンテナンスし続けるのが一般的です。利用率やキューの待ち行列の長さといったメトリクスに基づくキャパシティのスケールアウト・スケールインと似ています。状況がしきい値を割ったときに、上述のリストにあげたようなオペレーションによって、理想的な状態に戻るようにメンテナンスし続けるのです。使った対応フローをとれるようにしています。状況を監視し、変化があったことを検知してアラートをあげる。追加購入などのオペレーションをする。状況ベースでの監視は、実際に状況が変わってからしか検知できないため、対応が後手に回ってしまいがちという弱点があります。よる費用削減効果が減ってしまうことになります。状況をベースとした監視に加え、以下のような対応フローも整えています。発行してアラートをあげる。理想に近い状態で運用することができています。よるダッシュボードや、実際の対応フローを見てみましょう。一覧することができます。取得できる値は、月に一回程度、変な値を示してしまう場合があるのですが、頻度も少ないですしそれは許容しています。このスクリーンショットでは、左上のグラフの谷になっている箇所ですね。自動的に立って場が作られ、そこで対応することになります。したりといったオペレーションをしたこともありました。ここからは実装の話に移ります。以下のトピックについて説明していきましょう。カバレッジによって知ることができます。どの程度有効になっているか、すなわちどの程度余らせることなく有効に使えているかを示す割合です。よってどの程度カバーされているかを示す割合です。維持できている状態が望ましいです。近い状態を保っているのが理想です。なるように買ってしまうと、将来的な利用の変化に対応することが難しくなります。余るといったことも起こるでしょう。考えたくはないですが、サービスのクローズによって不要になるリソースもあるかもしれませんね。メンテナンスするのがよいでしょう。確認でき、フィルタを駆使することでリージョンやサービスごとに値を絞り込めます。よってどの程度コストを節約できたのかといった情報も表示できます。情報をサマライズしたビューが用意されています。確認することができて便利です。よって自在に引き出すことができます。用いてダッシュボードを作ることも可能になっています。状況を確認できることがわかりました。次はそれらのメトリクスに対してアラートを仕掛けることを考えていきましょう。特定のメールアドレスに送ることができます。簡単に通知できるようになったようです。Managementこれはこれで便利ですが、以下の理由で今回の要件を満たせないと考え、自前で仕組みを用意しようと判断しました。判定するように設定すると、しきい値を割っている間、毎日通知が届いてしまう。通知の細かい制御ができない。日以上しきい値を割り続けていたら」のようなアラートを設定できない。通知だけでは見落としが発生する可能性が高く、後半の冒頭で述べたような対応フローを実現できない。中心とした、以下のような仕組みを構成しました。仕組みとしては非常に素朴ですが、よく動きます。カスタムメトリクスにプロットする。いった他の様々なメトリクスと組み合わせたダッシュボードを作ることもできて便利というメリットもあります。立てるようにするなど、もう少し複雑な構成になっています。そのため、上記の構成図はエッセンスを抜き出したものとなっています。利用してデプロイしています。なく、汎用的に利用できるように実装しているからです。立てることができるようになっています。中身を見ていきましょう。ri-utilization-plotterソースコードはこちらにあります。実行して手元で実行できるようになっています。メトリクスの名前を指定します。カバレッジもプロットすることができます。テンプレートしか含まれていません。実環境へのデプロイの設定を分離するためにこうなっています。テンプレートを別途用意する必要があり、これは社内固有の設定も含むため社内のリポジトリで管理されています。変換することで生成しています。なるようにしています。中でそれを呼び出すイメージです。ファイルのパスを差し込むようにしています。パワフルがゆえに何でもできてしまうという側面もあり、用法用量を間違えると複雑怪奇な設定ファイルができてしまうというダークサイドもありますが、個人的にはとても気に入っています。github-issue-openerソースコードはこちらにあります。テンプレートして記述することができます。メッセージのメッセージがそのままレンダリングされます。見れば、雰囲気が掴めるでしょう。見れば雰囲気が掴めるかと思います。設定することで実現できます。ようにして値を取り出してレンダリングすることができます。して管理されています。環境変数に設定しています。日前、当日にメールを送信する機能が実装されたため、それを利用することにしました。メールを送るだけだと、後半の冒頭で述べたような対応フローをとることができません。見逃してしまうこともあるでしょう。誰かにアサインされるようにしました。設定されたアラートが残っていないか振り返りを行っているため、作業が漏れることもありません。中心とした仕組みにより、後手になりつつも異常に気づくことができます。したりする場合は、現在どの程度オンデマンドインスタンスの利用があるのかを把握して、プランを立てる必要があります。ようなスクリプトを利用したりしています。正規化した台数を算出します。職人の技で丹精を込めてプランを立てていますが、将来的には自動化したいと考えています。素朴にやろうとするとすべての購入プランを網羅して最適な解を選ぶことになりますが、それはおそらく現実的ではありません。問題を適切にモデル化して既存のアルゴリズムを利用するなど、賢くやらねばなりません。ある意味で競技プログラミングのようですね。維持管理のための仕組みについて説明しました。スポットインスタンスの積極的な利用に次ぐコスト最適化の強力な武器ですが、日々の手入れを怠るとその力を十分に発揮することができません。この記事では、クックパッドではその武器をどのように手入れしているかということについて説明しました。管理にお悩みの方は、参考にしてみてはいかがでしょうか。使ったセキュリティログ検索基盤の構築サービス特性にあった検索システムの設計戦略クックパッド採用説明会「クックパッドはサービスの作り手を採用したいんです。/*}*/スターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。