コネヒト開発者ブログ

コネヒト開発者ブログ

iOS/Android 開発Tips共有会 potatotips #72 をオンライン開催しました

本年もよろしくお願い致します。コネヒトのテクノロジー推進グループというところでアプリケーションエンジニアをしているaboyです。

2021年もコネヒトは技術コミュニティに貢献していくぞ、ということで、昨年12月ではありますが iOS/Android 開発Tips共有会 potatotips #72 を主催させていただきました。

potatotips.connpass.com

12月は師走、お忙しい中参加してくださった方々、本当にありがとうございました。

ちなみに、コネヒトにとって12月は再出発の月でした。新しいビジョンを掲げ、コーポレートサイトもリニューアルしました。また、コネヒトにおけるテクノロジーへの「態度」を表明した羅針盤 Connehito Tech Vision を公開したタイミングでもあったため、その紹介もさせていただきつつ。

connehito.com

以降は発表スライドのまとめと、簡単な感想を書いています。

なお当日の様子は togetter してくれた方がいました。ありがとうございます!

togetter.com

発表スライドと一言感想

僕は CI サービスを Travis CI から Bitrise へ移行したことについて発表しました。元々 fastlane で自動化されていたところをそのまま活用し、ワークフローは fastlane ステップを中心に組み立てるだけ。移行コストは低かったです。


同僚の @tommykw さんは Kotlin Compiler Plugin の作り方を解説してくれました。ドキュメントが無いそうですが代わりとなりうるオススメの動画を紹介してくれました。

www.droidcon.com


iOS 14 における Universal Links の変更点について。 Apple CDN 経由に変わり、クローラーからアクセスできるサイトで app-site-association を配布する必要があるみたいです。


はじめての発表ということでしたがとてもスムーズでした! あとで書く


HorizonCalendarというカレンダー UI ライブラリの tips を共有してくれました。僕はこのライブラリを知らなかったのですが、自前で実装すると色々と面倒な UI を実現できるので便利そうでした。メソッドをチェーンさせて書けるのが Flutter や SwiftUI ぽくて個人的に好きです(Flutterは Dart のカスケード記法のことです)。

github.com


Flutter や Kotlin Multiplatform Mobile 等のマルチプラットフォーム開発フレームワークを使うにしても、プラットフォームごとの知識はあるに越したことはないので、入門する際の参考にしたいと思いました。


Twillio 使ったことないので新鮮でした。主題から逸れますが CMTime と TimeInterval のインターフェースが用意されていますがどう使い分けるのがいいのでしょう?コネヒトで動画再生まわりを実装したときは、CMTime を使って再生時間を取得していました。


Flutter における状態管理手法が、僕の中では「Google も使ってるし BLoC でおk」のイメージからアップデートできていなかったので、久しぶりに状態管理について聞けて良かったです。アーキテクチャの選定/命名理由の言語化も丁寧だなーと思って聞いてました。


下から出てくるメニューに限らず、いわゆるアコーディオン UI も簡単に実装できたりするんですかね。この方の著書『1人でアプリを作る人を支えるSwiftUI開発レシピ』も読みつつ SwiftUI 勉強したいと思いました!


Android は全くわかりませんが広く捉えるとプログラミングにおいて重要なエラーハンドリングの話だ...と思いながら聞いていました。スライドの最後に書いてある「Lint は無視しない」は大事です。

参加者の皆さんありがとうございました!

最後に、イベント中流していたハワイの BGM はこちらです。

www.youtube.com


コネヒトでは Android エンジニアなど募集していますので少しでも気になった方は一緒にお話しましょう!

hrmos.co

RxSwift 6.0の主な変更点

こんにちは!コネヒトでiOSエンジニアをやっていますyanamuraです。

RxSwiftの6.0が公開されました。

公式のWhat's new in RxSwift6はこちらです。

What's new in RxSwift 6 ? - DEV Community

細かいdiffはこちらをご覧ください。 https://github.com/ReactiveX/RxSwift/compare/5.1.1...6.0.0

------------------------------✂--------------------------------------

ここでは、RxSwift6.0にアップデートすると影響受けそうなところから見ていきたいと思います。

Breaking Changes

deprecatedだったものが消されました

変更はこちら

Deprecated

以下のものの命名が変更されました。コンパイルは通りますがwarningになります。

  • Rename catchError(:) to catch(:).
  • Rename catchErrorJustReturn(:) to catchAndReturn(:).
  • Rename elementAt(_:) to element(at:).
  • Rename retryWhen(_:) to retry(when:).
  • Rename takeUntil(:) to take(until:) and takeUntil(behavior::) to take(until:behavior:).
  • Rename takeWhile(:) to take(while:) and takeWhile(behavior::) to take(while:behavior:).
  • Rename take(_:) duration overload to take(for:) (e.g. take(for: .seconds(3))).
  • Rename skipWhile(_:) to skip(while:).
  • Rename takeUntil(_:) to take(until:).
  • Rename observeOn and subscribeOn to observe(on:) and subscribe(on:).

Single

RxSwift5まではSingleはsubscribeするとSingleEventという独自のResultみたいなものを返していましたが、これがResultに変わりました。

// RxSwift 5

public enum SingleEvent<Element> {   
    /// One and only sequence element is produced. (underlying observable sequence emits: `.next(Element)`, `.completed`)  
    case success(Element) 

    /// Sequence terminated with an error. (underlying observable sequence emits: `.error(Error)`) 
    case error(Swift.Error)   
}
// RxSwift 6

public typealias SingleEvent<Element> = Result<Element, Swift.Error>

これまでは .error, onError としていたところを .failure, onFailure に変更しないとwarningになります。

変更のコミットはこちら

New

全てのReactiveCompatibleなオブジェクトのプロパティでBinderの実装が不要に

これまではclassのプロパティを

class MyView: UIView { 
    var title: String
}

このようにbind(to:)できるようにするには

viewModel.title.bind(to: myView.rx.title)

下記のような記述を書く必要がありました。

// RxSwift 5

extension Reactive where Base: MyView {
    var title: Binder<String> {
       Binder(base) { base, title in 
           base.title = title
       }
    }

RxSwift6ではReactiveCompatibleなオブジェクト(.rxの使えるオブジェクト)ではこの記述は不要になります。

変更のコミットはこちら

withUnretained

ObservableTypeに withUnretained が追加されました。

これまでは[weak self]をいちいち書く必要がありましたが

viewModel.importantInfo
    .subscribe(onNext: { [weak self] info in 
        guard let self = self else { return }
        self.doImportantTask(with: info)
    })
    .disposed(on: disposeBag)

withUnretainedを使うことでoptional bindingが不要になります。

viewModel.importantInfo
  .withUnretained(self)
  .subscribe(onNext: { owner, info in 
    owner.doImportantTask(with: info)
  })
  .disposed(by: disposeBag)

変更のコミットはこちら

decode(type:decoder:)

Observabledecode(type:decoder:) が追加されました。

decodeがメソッドチェーンですっきりとかけるようになりました。

func test() {
    let rawJSON = """
        [
          {"id": 1, "name": "Foo", "country": "Foo"},
          {"id": 2, "name": "Bar"}
        ]
        """.data(using: .utf8)!

    Observable
              .just(rawJSON)
              .decode(type: [FakeObject].self, decoder: JSONDecoder())
}

private struct FakeObject: Equatable, Decodable {
  let id: Int
  let name: String
  let country: String?
}

変更のコミットはこちら

driveとemitで複数のobserverにbinding

driveとemitでは一つにしかbindingできませんでしたが、複数できるようになりました。

viewModel.string.drive(input1, input2, input3)
viewModel.number.emit(input4, input5)

変更はこちらこちら

Infallible

RxSwiftにInfallibleが追加されました。

errorを流さないObservableです。似たようなものにRxCocoaのDriverやSignalがありますが、これらと違うのはメインスレッドは保証されないのとresourceをshareしたりreplayしない点です。

実装はこちら

ReplayRelay

ReplayRelayが追加されました。

PublishRelay, BehaviorRelayと同様にReplaySubjectのwrapperでerrorやcompleteを流しません。

変更のコミットはこちら

distinctUntilChanged(at:)

distinctUntilChanged(at:) が追加されdistinctUntilChangedにkeyPathを使うことができるようになりました。

        struct TestObject: Equatable {
            let value: Int
            let other = ""
        }

        Observable<TestObject>
            .create { observer in
                observer.onNext(TestObject(value: 1))
                observer.onNext(TestObject(value: 1))
                observer.onNext(TestObject(value: 2))
                return Disposables.create()
            }
            .distinctUntilChanged(at: \.value)
            .subscribe(onNext: {
                print($0.value)
            })

変更のコミットはこちら

UIApplication.rx

UIApplicationのLife cycle Eventなどが追加されました。

追加されたものはこちらです。

  • didEnterBackground
  • willEnterForeground
  • didFinishLaunching
  • didBecomeActive
  • willResignActive
  • didReceiveMemoryWarning
  • willTerminate
  • significantTimeChange
  • backgroundRefreshStatusDidChange
  • protectedDataWillBecomeUnavailable
  • protectedDataDidBecomeAvailable -userDidTakeScreenshot

変更のコミットはこちら

サービスとシステムの健全性に保つためのAndroidチームの取り組み色々

こんにちは!2017年11月にAndroidエンジニアとしてjoinした関根です。気づけば入社4年目に突入しました。 さて今回は、弊社サービスのママリ改善を担当するAndroidチームでやっていることや始めていることを、年末の棚卸しを兼ねて紹介してみようと思います。

この記事はコネヒト Advent Calendar 2020 16日目の記事です。

やっていること

まずは今年までに既にやっていることの中から2つの動作チェックサポート対象のアプリバージョンやminSdkVersionの運用について紹介をさせていただきます。

2つ動作チェック

言わずもがなですが、サービスやシステムを健全に保つためには、意図した挙動が守られていることを事前に確認しておく必要があります。 Androidチームでは大きく分けて2つ動作チェックのタイミングがあります。

プルリクエストでの確認

ひとつめはプルリクエストを送るタイミングです。 下記のようなテンプレートを用意して、レビュアー/レビュイーの間の共通認識をとり、認識の齟齬を減らしミスを減らす工夫をしています。

## 目的
今回のPRの目的を書く。関連issueを貼ることが多い

## やったこと
このPRの作業内容を書く

## テスト項目
動作確認する内容や手順を書く

## チェック項目
- [ ] File Changedのセルフレビュー
- [ ] 6系で動作が問題ないこと
- [ ] 7系で動作が問題ないこと
- [ ] 8系で動作が問題ないこと
- [ ] 9系で動作が問題ないこと
- [ ] 10系で動作が問題ないこと
- [ ] デザインが崩れていないこと
- [ ] Activity破棄設定にして動作が問題ないこと
- [ ] 不要なリソース削除。`$ ./gradlew removeUnusedResources`

## その他
特に見てほしいポイントや、その他レビュワーに伝えたいことなどがあれば記載

テンプレートのチェック項目を全て確認をするのはレビュイーが多いですが、レビュアーも必要に応じて動作確認をしています。

リリース前チェック

もう一つはリリース前のリグレッションテストです。 こちらはプルリクエストの場合と違い、既にある機能を守れているかを確認するテストとなります。

確認する項目

サービスの重要な機能にしぼり、約70の項目を確認しています。 プロダクトマネジメントを担当するメンバーと相談し、ユーザーやクライアントに与える価値の大きいものを抜粋してテストをしています。

確認するタイミング

アプリリリース前に実施しています。かかる時間は一時間弱です。

機能が増えるタイミングで項目が追加されることも多いため不定期で棚卸しを実施しています。今は人力での動作確認をしていますが、今後は自動化検討して、効率化をしていきたいと考えています。

サポート対象のアプリバージョンやminSdkVersionの運用

Android開発ではminSdkVersionとの向き合い方に悩まされることが多いと思います。コネヒトでも長い間明確な運用ルールは決めていなかったのですが、2019年度のシステム安定化という部署の目標をきっかけにルールの整備が始まりました。

導入の背景

それ以前は、ルールがないことで全ての環境の全ての問題を同じように対応することが多く、サービス改善で起こるシステムの問題に対して「過度な萎縮」が起こり始めていました。 しかし、ユーザーに価値を提供し続けるにはシステム改修に萎縮せず気軽にリリースをし続けることが大事だと考えています。他方、ビジネス数値からみると、サポート範囲を狭めることはネガティブに捉えられがちでなので、「守るべき範囲」を明確に定義し過度な萎縮を防ぐことが「ユーザーに価値を提供し続ける」ことに繋がるという前提で議論を進め、導入に至っています。現在は以下のルールで運用されています。

基準

ルール名 対応内容 基準値
非推奨環境 設計や実装時の考慮から外してOK
不具合やエラー発生時に通常issueとして扱い対応可否を決める
DAU500人以下の環境
レガシーアプリver. バージョンアップを誘導する リリースから2年経過したアプリVer.
レガシーOS minSdkVersionの対象外にする
※2~3週間前にお知らせでアナウンスをする
OS毎MAUが2%以下のOS

見直し時期

  • クオータ毎
    • 3月、6月、9月、12月

見直し事項

  • サポートバージョンの基準値の見直し
  • サポートバージョンの環境の更新

実施ステップ

  1. 開発部でサポート対象外となる環境を計測する
    • 特定期間のMajorバージョンシェア
    • 特定期間でDAU1000以下のMajorバージョン
    • 2年前のバージョン番号
  2. 社内に共有してスケジュールを組む
    • ex). レガシーOS/レガシーアプリver対応:事前に対象ユーザーのお知らせに案内を掲出
  3. スケジュールに沿って遂行する

あくまでも一定の基準であるため、閾値を下回ったから直ぐに対応を実施するわけではなく、開発効率のバランスやサービスに与える影響をプロダクトマネジメントを担当するメンバーと相談しながら進められるように工夫をしています。

はじめている事

最後に、これからはじめていくことの中からrenovateの導入について紹介します。今月に入ってから着手し始めたもので、試行錯誤の真っ最中です。

依存ライブラリのアップデートを効率化

renovateを一言で紹介すると、依存ライブラリのアップデートを検知してPRを投げてくれるOSSです。 Androidの依存ライブラリは更新も早く、人力でアップデートしていくのは、至難の技だと感じており、 この課題を解決するために、renovateで自動化をし、運用を楽にすることを考えています。 余談ですが、以前は gradle-use-latest-versions-pluginをGithub Actionsで実行し、PRを出す運用の準備をしていましたが、別のプロジェクトで仕様していたrenovateでも同じことが実現できることがわかり乗り換えをしています。 この取り組みの中から1つの工夫を紹介します。

PRをまとめる

renovateではデフォルト設定だとパッケージごとにPRが送られてきます。 依存ライブラリが多いとPRが多くなりすぎることが懸念されたので、minorとpatchアップデートは1つにまとめるようにルールを決めています。 renovateの設定ファイルに下記の設定を追加することで実現ができます。

"packageRules": [
        {
            "packagePatterns": [
                "*"
        ],
            "updateTypes": [
                "minor",
                "patch"
        ],
            "groupName": "all non-major dependencies",
            "groupSlug": "all-minor-patch"
        }
]

なお、majorバージョンは変更が大きく、プロダクトへの影響も大きいため、個別のPRでしっかりと検証する必要があると判断し、対象をminor、patchのみにしぼっています。

最後に

いかがでしたでしょうか?コネヒトでは今回紹介したように、各々のメンバーが工夫をしながら業務改善を楽しんでいます。そんなコネヒトでは積極的にエンジニアを募集しているので、是非一度話を聞きにきてください!

hrmos.co

Bitrise を触ってみた所感

こんにちは!アプリケーションエンジニアのあぼです。

コネヒトでは iOS アプリの CI ツールとして Travis CI を使っていましたが、先日プランが変わったこともあり、定額の Bitrise に移行しました。元々 fastlane で自動化していたので、Bitrise ではキャッシングなどのワークフローを組み立ててメインの処理は fastlane のコマンドに任せるという感じで動かしています。ですので移行自体もそこまで苦ではなかったなという印象です。

今回ははじめて Bitrise を触ったので自分の所感を簡単に書いていこうと思います。コネヒト Advent Calendar 2020の記事です。

導入初期の色々試したいときに GUI でガリガリいじれる

Bitrise はワークフローの設定などが書かれた bitrise.yml を Bitrise.io で管理する方法とリポジトリ管理する方法が選べますし、後からでも変更可能です。個人的に導入初期は yml のこと考えずに一気にガリガリ弄って試したいので、その点でラクでした。そしてある程度動くことが確認できて正式にチームに展開する際に GitHub のリポジトリ管理に切り替えています。GUI で弄った bitrise.yml をそのままダウンロードしてリポジトリに追加すれば良いので切り替えも簡単にできました。

f:id:aboy_perry:20201214162452p:plain
bitrise.ymlの管理方法

bitrise.yml を自分で書く場合にドキュメントが必要なのですが、探してみた感じだと Bitrise CLI のドキュメントを参照すればだいたい網羅されてそうな気がします。CLI 自体 OSS なのでソースコードを読んで調べることもできますね。Go で書かれているようです。

devcenter.bitrise.io

スタック(イメージ)の更新・削除が早い

スタックの更新と削除ポリシーを見る限り、他の CI と比べて古いスタックが deprecated になるタイミングや、削除されるタイミングが早そうです。例えば直近だと Xcode11.x は 11.7 以外のマイナーバージョンが順次 deprecated になり、削除されていきます。Circle CI の場合 Xcode11.x は Xcode13 のリリースが始まってから順次 deprecated になっていきます。一方で、Bitrise は新しいバージョンのスタックが使えるようになるのも早い印象*1なので、そこは嬉しいですね。

Travis CI に関しては更新と削除ポリシーについてそれらしい記述が見つけられなかったのですが、現状用意されている環境一覧を見る限り Xcode7.3 があり、かなり古いバージョンまで保持しているようです。(ちなみに前述のリンク先のページでは Xcode11.7 が無い…のですが、チェンジログには対応したと書いてありました)

会社や個人の状況によって、スタックの更新・削除ポリシーが要件と合わず使いたくても使えないということも起こりそうですが、コネヒトの場合は運用していけそうと判断しました。

ビルドの結果がステップごとに表示されて見やすい

ステップごとに折り畳まれてたり、かかった時間が一覧で見れるのでパッと見で見やすいと感じます。ビルド時間を削減しようと思った時にステップを細かくわけてボトルネックを探ったりするのに使えそうですね。

f:id:aboy_perry:20201214165209p:plain
ビルドの結果がステップごとに表示される

キャッシュが便利

Bitrise のキャッシュはブランチごとに特定のディレクトリを保存しておけるもので、Bitrise.io Cache:PullステップとBitrise.io Cache:Pushステップを組み込んで簡単に使うことができました。該当のブランチに有効なキャッシュが無ければ Bitrise 上でデフォルトブランチに指定しているブランチのキャッシュを取りに行く仕様です。Travis CI ではプルリクエストトリガーの際にターゲットブランチのキャッシュを取りにいく点が違っていますが、ほとんど同じ感覚で使うことができそうですね。

コネヒトでは現在 ruby (rbenv)、Gem、CocoaPods で管理しているライブラリ、Swift Package Manager 管理しているライブラリをキャッシュしています。

devcenter.bitrise.io

おわりに

というわけで今回は Bitrise を触ってみて感じたことを書きました!まだ細かい課題ややりたいこともあるので色々試していきたいなと思います!


そして宣伝です!12/12 (火) に potatotips (iOS/Android のオンラインLT会) を開催予定です。空いている枠は先着順で入れるのでご予定合う方はぜひご参加ください〜!

potatotips.connpass.com

*1:Bitrise の Twitter をウォッチしているとそんな印象があります、あくまで印象

CakePHPを使った実装で悩んだときに見ている情報源

こんにちは! @fortkle です。
この記事は コネヒト Advent Calendar 2020 8日目 の記事です。

f:id:fortkle:20201208110038p:plain
https://cakephp.org/jp

はじめに

コネヒトではサーバーサイドの言語として主にPHP、フレームワークでいうとCakePHPが採用されています。 公式のドキュメントにもある通り、CakePHPは「設定より規約」的な思想が随所に感じられるフレームワークです。

私たちは「設定より規約」(convention over configuration) という考え方に賛成です。 CakePHP の規約を習得するには少し時間がかかりますが、長い目で見ると時間を節約していることになります。 規約に従うと自由に使える機能が増えますし、設定ファイルを調べまわってメンテナンスするという悪夢からも 開放されます。 規約によって開発が統一感を持つため、開発者が加わってすぐに手伝うということがやりやすく なります。
CakePHP の規約 - 4.x

日々の実装の中で「どうやって書けばスマートかな?」や「Cakeだとどうやって書くのがCakeWayっぽいかな?」だったりを考えながら手を動かすことが多いため、今日はそういった際に役に立つ情報源をまとめてみます。

ちなみに私が探しに行く順番で、上から順に紹介していきます。

情報源

公式ドキュメント(Cookbook)

最初というか、実装に悩む以前にCakePHPを触るのであれば一通り公式ドキュメントを流し見しておくことをおすすめします。 「設定より規約」という思想のフレームワークにおいて、「知っていること」が何よりも重要になるからです。

CakePHPの公式ドキュメントは内容の充実度もさることながら、ボランティアベースで行われている日本語への翻訳作業も継続的に実施されていて大変素晴らしい・・というかお世話になっております。

ドキュメントはcakephp/docsで管理されています。翻訳に興味がある人は以下の記事を参考にしてみてください。

公式APIリファレンス

CakePHPは公式のドキュメントであるCookbookの他に、APIリファレンスをHTMLの形で公開しています。これも実装に悩む以前に読んでいてほしいものですね。英語で言えばAPIは英単語、英会話を楽しむ前に最低限の語彙力を鍛えましょう!

エンジニアなら全員読んだことがありそうな『リーダブルコード』にも以下のような記述があります。

 プログラマというのは、既存のライブラリで問題を解決できることを知らないことが多い。あるいは、ライブラリで可能なことを忘れていることが多い。ライブラリの機能を熟知して、実際に活用することが大切だ。
 ここでささやかな提案だ。たまには標準ライブラリのすべての関数・モジュール・型の名前を15分かけて読んでみよう。標準ライブラリというのは、C++標準テンプレートライブラリ(STL)やJavaのAPIやPythonの組み込みモジュールなどのことだ。
13.4 身近なライブラリに親しむ 『リーダブルコード』 P.172

厳密にはフレームワークのAPIリファレンスよりは低いレイヤーの話だと思いますが、伝えたいことは同じです。全てを覚えるのではなく、頭の中にインデックスを作り、実装で悩んだときに参照できるようにしておきましょう、ということです。

社内の類似技術スタックのリポジトリ

(全員が参考にできないのでサクッといきますが)社内に同じような技術スタックのリポジトリがある場合はそちらの実装も参考にしてみましょう。 大抵の悩みは先行しているリポジトリで解決されていたりします。

cakephp/cakephpリポジトリ

特におすすめなのは「テストコードを読むこと」です。
公式ドキュメントを読んでも使い方が分からなかったり、自分たちがやりたいカスタマイズ方法が分からなかったりする場合はあると思います。 そんなときにテストコードを読むと、テスト対象であるSUTが「一体何を目的に実装されたコードなのか?」を明示されながら使い方まで理解することができるからです。

個人的には、テストコードのスマートな書き方を求めてコアコードを見に行くことが多いです。

(コラム) 思想を理解する

フレームワークの思想を理解するにはより詳細にissueなど実装当時の情報を深ぼるのもおすすめです。特にlabel:RFCだけでも見ておくとよいかもしれません。
例えば、CakePHP3でガラッと変わったバリデーション周りの変更はこのissueに背景が書いてありますし、Traitの使い方の見直しについてもこのissueで議論を垣間見ることができます。

他にもwikiにあるロードマップを見たり、コアコントリビューターのmark storyのwikiには実装の草案・アイデアが公開されていたりします。

FriendsOfCake/awesome-cakephp リポジトリ

CakePHPの良さそうなライブラリやシステムのリンク集です。
用途別に分類されているので、関係がありそうなセクションのリポジトリを眺めにいくことが多いですね。 弊社に関係するライブラリもいくつか記載されています💪

ちなみに、 FriendsOfCake はCakePHPの中の人達が中心となっている開発者グループで、品質の比較的高いCakePHPプラグインやリソースが提供されているため、比較的安心して実装を参考にできるかなと思います。

CakePHP Slackグループ

これまで紹介した情報源を調べても分からなかったり、数時間悩んだりするなら「人に聞く」というのもおすすめです。
周りに詳しい人がいない場合は、CakePHPコミュニティのSlackグループがあるのでそちらを活用してみるのはどうでしょうか。

#japanese チャンネルがあるので日本語で相談もできますね!
CakePHP Slack から誰でもjoinできます。

コアデベロッパーの関連サイト(2020/12/08 追記)

CakePHPのコアデベロッパーが個人でCakePHPに関する情報発信をしているケースが多々あるのでそちらも参考になります。いくつか例をあげます。

おわりに

ここまで参考になりそうな情報源を紹介してきました。
実際にはもちろんQiitaやZenn、個人ブログなども参考に見ることが多いですが、参考に値する確かな情報源を持っておくことは重要です。 いろいろなコードを読み、理解して、よりスマートな実装ができるように鍛錬していきましょう!

宣伝! イベントやります!

コネヒトでは12/17にオンラインイベント開催予定です。 サービス開発が大好きなエンジニアはもちろん、事業会社で働いてみたいエンジニアやtoC向けサービスに興味がある方はぜひご参加ください〜!

connehito.connpass.com

iOS14からはaddTargetじゃなくてaddAction

こんにちは、コネヒトでiOSエンジニアをやっていますyanamuraです。

これは iOS Advent Calendar 2020 の 4日目の記事です。

TL;DR

UIKitのUIControl系のView(UIButtonなど)ではタップ時のアクションをコードで実装するときは、標準のAPIだとaddTargetを用いる必要がありました。addTargetだとclosureが使えずいちいち関数を定義しなければならなかったり、@objcをつける必要があったりと面倒でした。

// addTargetでやるパターン
@IBOutlet weak var button: UIButton!

override func viewDidLoad() {
    super.viewDidLoad()

    button.addTarget(self, action: #selector(doSomething), for: .touchUpInside)
}

@objc func doSomething() {
    print("do something")
}

iOS14でついにUIControlにaddActionという新しいAPIが追加されこの面倒くささが解決しました!(SwiftUIがでているこの状況では今更感がありますが・・)

このようにシュッとかけるようになっています。

button.addAction(.init { _ in print("do something") }, for: .touchUpInside)

(ちなみにコネヒトでは現在RxSwift(RxCocoa)を使っているので正直addActionの恩恵にあずかることはなさそうです

詳細

UIControlの新しいAPIのaddActionではactionをselectorではなくUIActionを受け取るようになっています。

// UIControl
func addAction(_ action: UIAction, for controlEvents: UIControl.Event)

UIActionはinitializerで多くの引数を指定できますが、必須なのはhandlerだけです。

// UIAction
convenience init(
    title: String = "",
    image: UIImage? = nil,
    identifier: UIAction.Identifier? = nil,
    discoverabilityTitle: String? = nil,
    attributes: UIMenuElement.Attributes = [],
    state: UIMenuElement.State = .off,
    handler: @escaping UIActionHandler)

UIActionHandlerは単なるaliasです。

// UIActionHandler
typealias UIActionHandler = (UIAction) -> Void

UIControlにはaddAction以外にもinitializerでactionを設定することもできるようになっています。https://developer.apple.com/documentation/uikit/uicontrol/3600494-init

// UIControl
convenience init(frame: CGRect, primaryAction: UIAction?)

使い方

わかりやすく書くとこうなります。

let action = UIAction(handler: { _ in print("do something")})
button.addAction(action, for: .touchUpInside)

省略して書くとこのようになります。

button.addAction(.init { _ in print("do something") }, for: .touchUpInside)

addActionが使えるView

addActionはUIControlのAPIなのでUIControlとUIControlを継承しているクラスでは使えます。

ですので、UIButtonをはじめ、UIDatePicker, UIPageControl, UISegmentedControl, UISwitch, UIStepper, UISlider, そして新たに追加されたUIColorWellでaddActionが使えるはずです。

参考

WWDC2020: Build with iOS pickers, menus and actions https://developer.apple.com/videos/play/wwdc2020/10052/

Lambdaのコンテナサポートに関する考察

こんにちは。インフラエンジニアの永井(shnagai)です。

AWS re:Invent今年も大豊作ですごいですね。まだ全部は追えてないんですが、良さそうなものがあればサービスに取り入れていこうと思いわくわくしています。

この記事はコネヒト Advent Calendar 2020 - Qiita 3日目の記事です。

今回は、試してみてる方は結構いそうなので、ざっとLambdaのコンテナサポートを触ってみた感じの所感を中心に書いていきます。

うれしいポイント

  • 今想像してる一番うれしいポイントは、lambdaがサポートしてる数多のAWSインテグレーションをトリガに好きな処理が動かせるところ(lambdaRuntimeAPIの存在を知りそう甘くないことを理解した)
  • ローカルの開発がやりやすくなるなー
    • SAMとか使って出来るけど。個人的にはlambdaの管理は煩雑
    • dockerで検証出来た方が楽
  • lambdaの設定とアプリケーションコード(コンテナ)を分離して管理出来るようになるので、コード化しやすいなとか

vs 既存のLambda

  • Lambdaの一番かゆいところは、チーム開発しようとするとデプロイフローがやや面倒という点があると思っている(AWS SAMとかをうまく使っていけば出来なくはないが、チーム全員で回すにはやや複雑と感じている)

pros

  • 今回、アプリケーションコードをDockerコンテナ内に閉じ込めることで、既存の開発フローに乗せてLambdaを開発出来るようになる未来が見えた
  • ハンドラがDockerのCMD句に置き換わるので、共通のイメージで開発して、Function毎にCMD句だけ上書きすることで管理コストが大幅に下げれそうな気がしている

cons

  • しいて言うなら、Lambdaのメリットにマネコン上からコード閲覧出来たりサクッと変えてお試しみたいなのが出来るのがあると思うが、それは出来ない(手軽さは少し落ちるかなというところ)

vs Fargate

  • サーバレスでコンテナを動かせると考えると、Fargateの代替になりえるなと考えるのは当然の流れだと思う

pros:

  • AWSのインテグレーションを簡単に使えるというLambdaの強みを享受
    • 例えば、下のサンプルでやったSQSトリガのワーカーの仕組みなんかは、Fargateでやる場合は、ポーリングの処理を書かないといけないがそれが不要になるのはうれしい
  • Fargateでコンテナ動かすには、ECSなりEKSのオーケストレーターも必要なわけなので、スポットで単発の処理動かしたいみたいな時はLambdaはより簡単で良い

cons:

  • LambdaRuntimeAPIをコンテナ側で実装しなければいけないので、どんなイメージでも動くわけではない。
    • AWSである程度の言語については、すでに用意されてはいる
    • インターフェースを統一しなきゃいけないのはそれはそうだな
    • 既存のアプリケーションの一部をLambda+コンテナで動かすのはちょっとハードルあるなというのが正直な印象ではある

ちょっとイメージと違ったところ

これは触ってみて感じたことなので、実は違う可能性もある。

  • コンテナイメージは都度pullではなく、イメージ設定時に固定されそう
    • 同一タグでECR更新して、起動時に最新のイメージ取得みたいな構成は出来なさそう(何回か試したけど、lambdaを更新した時にイメージがずっと使われてそうだった)
    • 「新しいイメージをデプロイ」する時にpullしてるように見える
    • 起動速度維持するために、キャッシュしてるのかなと想像
    • コンテナビルドとLambdaの更新をセットでやらないとダメそう

で試してみる

今回は、SQSをイベントソースとして、SQSキューが入ったら中身を出力するという簡単な内容(疑似的なワーカー)

こちらのチュートリアルをベースに実行

docs.aws.amazon.com

コードはこちら

github.com

コンテナの準備

$ docker build -t [アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/tst-shnagai:test .

## 手元で動作確認
$ docker run --rm [アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/tst-shnagai:test

hogehoge

## ECRへのpush(ログイン込)
$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin [アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com && docker push [アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/tst-shnagai:test


Lambdaの設定

作成(コンテナイメージを選ぶ)

f:id:nagais:20201203100953p:plain

  • 手元にあった適当なpython動くコンテナで最初試したけど、それをただ実行させるとかは出来ないみたい(lambdaRuntimeAPIが必要で、呼び出しI/FもLambdaにそる)

f:id:nagais:20201203100356p:plain

  • IAM Roleとして AWSLambdaSQSQueueExecutionRole を追加するのを忘れずに。

SQSの準備

aws-cliでサッと作る

$ aws sqs create-queue --queue-name lambda-test
{
    "QueueUrl": "https://ap-northeast-1.queue.amazonaws.com/[アカウントID]/lambda-test"
}

先程作ったLambdaのイベントソースに上記のSQSを登録

テスト

$ aws sqs send-message --queue-url https://ap-northeast-1.queue.amazonaws.com/[アカウントID]/lambda-test --message-body "lambda-containerのテスト"

無事に動いた。

f:id:nagais:20201203100305p:plain

クラメソさんの記事が概要理解するのにめちゃ役立ちました!!

dev.classmethod.jp