- 投稿日:2021-07-15T15:28:17+09:00
[Swift]for文とforEachのどちらを使うか
※この記事はSwift by Sundellの内容を日本語に翻訳したものです。 ArrayやSetなどのコレクションを反復処理する場合、最初は、従来のforループを使用するか、クロージャーベースのforEachメソッドを使用するかは、好みとスタイルの問題のように思われるかもしれません。これらの2つのメカニズムが非常に類似していることは確かですが、いくつかの点で異なります。 これらの違いの1つは、forループの反復がコードの制御フロー内で直接実行されることです。これにより、これらの反復をより正確に制御できます。たとえば、continueキーワードを使用して任意の時点で次の要素にスキップするか、breakを使用して反復を完全に中断するかを選択できます。 func recentArticles(among articles: [Article]) -> [Article] { var results = [Article]() for article in articles { guard !article.isDraft else { // すぐに次の要素にスキップします。 continue } results.append(article) guard results.count < 5 else { // 反復を中断します。この場合、ここに直接戻ることもできます。 break } } return results } forループの反復が(上記のように)フィルタリングに使用されるguard(またはif)ステートメントで始まる場合、代わりにwhereベースのパターンマッチングを使用してそのロジックを実装することもできます—次のように: for article in articles where !article.isDraft { results.append(article) guard results.count < 5 else { break } } 上記の種類の制御フロー関連の機能は、forループを非常に強力にする大きな部分ですが、そのレベルの制御が必要ない場合は、forEachの呼び出しを使用すると、コードが少し単純に見える可能性があります。たとえば、ここでは、配列の要素ごとに新しいビューを追加しています。これは、forEachを使用して非常にエレガントに実行できます。 func addArticleViews(for articles: [Article]) { articles.forEach { article in let articleView = ArticleView() // クロージャーの article を使用して新しいビューを設定します。 ... view.addSubview(articleView) } } ただし、Swiftのファーストクラス関数機能を使用する方がおそらくさらにエレガントです。上記のメソッドをリファクタリングして単一のArticleのみを受け入れ、そのメソッドをforEachの呼び出しに直接挿入します。 class ArticleGroupViewController: UIViewController { private let articles: [Article] ... override func viewDidLoad() { super.viewDidLoad() ... articles.forEach(addArticleView) } private func addArticleView(for article: Article) { let articleView = ArticleView() ... view.addSubview(articleView) } } 要約すると、forループを使用すると、反復をより高度に制御できます。一方、forEachを使用すると、一度開始すると反復を停止することはできませんが、クロージャとファーストクラス関数の機能を利用できます(エラーをスローすることは別として)。 元の記事 Picking between a for loop and forEach Swift by Sundell
- 投稿日:2021-07-15T15:01:20+09:00
[Swift]同じ名前の変数に式を割り当てる
※この記事はSwift by Sundellの内容を日本語に翻訳したものです。 Swift 5.4の新機能: selfを使って手動で明確にすることなく、同じ名前の式に割り当てられるローカル変数を作成できるようになりました。 たとえば、次のItemListViewControllerには、単にitem(at :)を呼び出すように選択した特定のIndexPathのアイテムを取得できるメソッドがあります。 class ItemListViewController: UIViewController { ... private func item(at indexPath: IndexPath) -> Item { ... } } Swift 5.3以前を使用しているときに、上記のメソッドを呼び出して、その結果をitemとも呼ばれるローカルのletまたはvarに割り当てる場合、コンパイラーが使用できるようにするには、メソッド呼び出しの前にselfを付ける必要があります。 2つを分けるには—次のように: class ItemListViewController: UIViewController { ... private func showDetailViewForItem(at indexPath: IndexPath) { let item = self.item(at: indexPath) ... } } ただし、Swift 5.4以降、これは不要になり、コンパイラは、selfを使用して参照するかどうかに関係なく、右側のアイテムシンボルがメソッドの呼び出しを参照していることを自動的に認識します。 class ItemListViewController: UIViewController { ... private func showDetailViewForItem(at indexPath: IndexPath) { let item = item(at: indexPath) ... } } おそらく、この新しい構文機能を使用する私のお気に入りの方法は、非同期コードの単体テストを作成するときです。これには通常、組み込みのexpectationAPIを使用してXCTestExpectationインスタンスを作成することが含まれます。このような期待の最も自然な名前は単にexpectationであることが多いため、次のことができるようになったのは本当に素晴らしいことです。 class ItemLoaderTests: XCTestCase { ... func testLoadingItem() { let expectation = expectation(description: "Loading item") ... } } 上記の機能が役立つもう1つのタイプの状況は、プロパティのローカルで変更可能なコピーを作成する場合です。これは、コピーするプロパティを参照するときにselfを使用せずに実行できるようになりました。たとえば、次のようになります。 struct Item { var title: String var prefix: String? var suffix: String? func fullTitle() -> String { var title = title // 従来は var title = self.title if let prefix = prefix { title = prefix + title } if let suffix = suffix { title += suffix } return title } } 確かに、これはSwiftコードの記述方法を完全に変える革新的な新しい構文機能ではないかもしれませんが、すべての機能や変更が必要なわけではありません。個人的には、このような「生活の質」の向上は非常に歓迎されており、特定の種類のコードをかなり軽量に感じさせることができると思います。 元の記事 Assigning an expression to a variable with the same name Swift by Sundell
- 投稿日:2021-07-15T13:48:33+09:00
【FCM・APNs】iOSで InvalidRegistration error になってしまう/ deviceTokenが無効の場合
はじめに Androidでは問題なくdeviceTokenやトピックを用いた通知の送信ができるのに、iOSでなぜかうまくいかないとき。 APNsで発行されるトークンと、FCMで使用するトークンは、実は異なる場合があるので注意が必要です。 SandBox と Production 根本的な原因を理解するためには、APNSがプッシュ通知にSandBoxとProductionの2つのサーバーを使用していることを理解する必要があります。開発段階でiOSデバイス上にアプリのデバッグバージョンを構築すると、SandBoxサーバーに接続し、そこからトークンを取得します。アプリのリリースビルドを作成すると、本番サーバーに接続されます。 デバッグビルドでアプリをテストしている場合、通知をテストするには、サンドボックスでテストする必要があります。トークンを受け取ったら、以下のGoogle APIを使用して、APNSトークンをFCMトークンに変換することができます。 リクエスト HTTP POST : https://iid.googleapis.com/iid/v1:batchImport HTTP HEADERS: Content-Type: application/json Authorization : key=YOUR_SERVER_KEY (FCMのコンソールから確認できます) リクエストbody { "application": "com.company.app", (YOUR_APP_PACKAGE) "sandbox":true, "apns_tokens":[ "7c6811bfa1e89c739c5862122aa7ab68fc4972dea7372242f74276a5326f...." ] } レスポンス { "results": [ { "registration_token": "ejXQlECjCeI:APA91bE7oaUhaFnGyl77lFrySdEaWxocM0oj81uNezACX1wsZXiTyL4OYo5ssvFjjWYpFymMVyqBccboVcwTTW2rvykOmV_CABDM7rTIRCiJFl_9ngf7SrDSYoFouwNj69JSwlH.....", "apns_token": "7c6811bfa1e89c739c5862122aa7ab68fc4972dea7372242f74276a5326f....", "status": "OK" } ] }
- 投稿日:2021-07-15T08:52:07+09:00
動画サービスをFirestore+CloudFunctionsで勝手に設計してみる ~ NobodySurf編
こんにちは。もぐめっとです。 最近本当に自分の写真のネタがなさすぎるので昔の写真を引っ張り出したりしてます。普通に滑ってるように見えますが、実は浮いてます。目の錯覚ショットですね。 今回は、昨日apple storeをみたらTodayで紹介されていたNobodySurfという超イケてるサーフィン動画をたくさん見れる超イケてるアプリが掲載されていたので、こちらのアプリをもしfirestoreでシステム構築したらどんな構成になるのか?という題材で勝手に設計してみたので僕が考えた設計を紹介します。 アプリ概要 アプリについて紹介します。 トップからは主にカテゴリ別の動画が見れ、動画詳細で動画をチェックし、気に入れば自分のプレイリストを作って保存することができます。 TOP 動画詳細 マイページ 検索はサーファー名、カテゴリ、フィン、波の大きさ、スタンス、性別などなどいろんな角度から検索をすることができます。 検索TOP 検索結果 Firestore設計 こんな構造を考えてみました movies: サーフィン動画 surfers: サーファー一覧 news: お知らせ users: アプリを使うユーザ情報 playlists: ユーザ保存したプレイリスト情報 movies: 実際に保存したmovieのコピー billingTransactions: 購入したときのレシートを保管 subscriptions: ユーザのサブスク状況 movies movie情報です。 movieのidからストリーミングするURLは生成することとします。 /movies/{movieId} movieId: 自動生成 Field Type Description 関連CloudFunctions createdAt Timestamp 登録日 updatedAt Timestamp 更新日 movieTitle String 動画タイトル surfers Map[String: DocumentReference] サーファー名とそのリファレンス filmers Map[String: DocumentReference] 撮影者名とそのリファレンス editors Map[String: DocumentReference] 編集者名とそのリファレンス filmEditors Map[String: DocumentReference] 撮影者兼編集者名とそのリファレンス presenters Map[String: DocumentReference] プレゼンター名とそのリファレンス producers Map[String: DocumentReference] プロデューサ名とそのリファレンス supporters Map[String: DocumentReference] サポーター名とそのリファレンス narrators Map[String: DocumentReference] ナレーション名とそのリファレンス writers Map[String: DocumentReference] ライター名とそのリファレンス musics Array[String] 使われてる音楽名 droneFootager [String] ドローン映像名 countries [String] 撮影された国 locations [String] 撮影された場所 series String シリーズ名 runTime Int 動画時間 years Array[Int] 撮影年代 surfboards Array[String] enum: shortboard, longboard, midLength, fish, softTop, gun fins Array[String] enum: finless, singleFin, twinFin, triFin, Bonzer waveSizes Array[String] enum: small, chestHead, overhead, big, huge stances Array[String] enum: regular, goofy genders Array[String] enum: man, woman, unknown otherCategories Array[String] enum: youth, legend relatedLinks Map[String: String] リンク名とURL surfers サーファー情報の管理です。 /surfers/{surferId} movieId: 自動生成 Field Type Description 関連CloudFunctions createdAt Timestamp 登録日 updatedAt Timestamp 更新日 surferName String 名前 surferTypes Array[String] サーファーだったり、撮影者だったり様々な肩書があるので配列で持ちます。enum: surfer, filmer, editor, fimEditor, presenter, producer, supporter, narrator, writer surfboards Array[String] enum: shortboard, longboard, midLength, fish, softTop, gun fins Array[String] enum: finless, singleFin, twinFin, triFin, Bonzer waveSizes Array[String] enum: small, chestHead, overhead, big, huge stances Array[String] enum: regular, goofy genders Array[String] enum: man, woman, unknown otherCategories Array[String] enum: youth, legend relatedLinks Map[String: String] リンク名とURL news 新着情報の表示用 /news/{newsId} newsId: 自動生成 Field Type Description 関連CloudFunctions createdAt Timestamp 登録日 updatedAt Timestamp 更新日 deletedAt Timestamp nullの場合は表示しない newsTitle String お知らせのタイトル movieTitle String? 動画タイトル playlistTitle String? プレイリストタイトル playlistReference DocumentReference? プレイリストのリファレンス message String? bannerの場合に表示するメッセージ url String? bannerの場合に遷移するURL viewType String? enum: card, banner どんな形式でお知らせを見せるか newsの取得は deletedAt != nullで表示するものだけ取得します。 users アプリを使うユーザ管理 下記はそれぞれFirebase Authenticationで解決できるのでここでは管理しません。 認証方法: Firebase Authenticationで管理 ユーザロール: Firebase Authenticationのcustom claimsで管理 Firestore側でなるべく個人情報などのsecureな情報は持たないように設計します。 /users/{userId} userId: firebase authenticationで認証したuid Field Type Description 関連CloudFunctions createdAt Timestamp 登録日 updatedAt Timestamp 更新日 needsNewsNotification Boolean お知らせ通知を送るかどうか favoriteSurfStyle String 好みのサーフスタイル enum: shortboard, longboard, alternative fcmTokens Array[String] 通知用のfcmToken配列。これを使ってpush通知を行う fcmTokenは今回はユーザ同士の読み取りがないためusersにもたせていますが、本来であればsecureな情報にあたるので、交流するようなSNS的なサービスであれば別途collectionを定義してやったほうがいいです。 users/playlists ユーザが保存したプレイリスト /users/{userId}/playlists/{playlistId} playlistId: 自動生成 Field Type Description 関連CloudFunctions createdAt Timestamp 登録日 updatedAt Timestamp 更新日 playlistTitle String プレイリスト名 movieCount Number 保存した動画数 onCreatePlaylistMovie, onDeletePlaylistMovie users/playlists/movies 保存した動画一覧。内容はmoviesと一緒 /users/{userId}/playlists/{playlistId}/movies/{movieId} movieId: 元のmovieのidと同等 users/billingTransactions ユーザが課金処理をしてwebhookからレシートを発行されたら保管する。 処理に失敗しても辿れるように必ず最初に保存だけしておく。 /users/{userId}/billingTransactions/{billingTransactionId} - billingTransactionId: Apple: originalTransactionId, Google: orderId Field Type Description 関連CloudFunctions createdAt Timestamp 登録日 onRequestSubscriptionApple,onRequestSubscriptionGoogle updatedAt Timestamp 更新日 onRequestSubscriptionApple,onRequestSubscriptionGoogle purchasedAt Timestamp 購入日 onRequestSubscriptionApple,onRequestSubscriptionGoogle platform String 購入したプラットフォームenum: apple, google onRequestSubscriptionApple,onRequestSubscriptionGoogle receipt String Apple: レシート, Google: json onRequestSubscriptionApple,onRequestSubscriptionGoogle productId String 商品ID onRequestSubscriptionApple,onRequestSubscriptionGoogle userReference DocumentReference コレクショングループで調べるよう onRequestSubscriptionApple,onRequestSubscriptionGoogle validatedData Map[String: any] レシートの検証結果の情報をいれておく onRequestSubscriptionApple,onRequestSubscriptionGoogle users/subscriptions /users/{userId}/subscriptions/{subscriptionId} - subscriptionId: Apple: originalTransactionId, Google: orderId platformからのwebhookを受け取ってサブスクリプション情報を更新する。 stripeのこの辺を参考にしてだいたい一緒だろうというのりで作った。 Field Type Description 関連CloudFunctions createdAt Timestamp 登録日 onRequestSubscriptionApple,onRequestSubscriptionGoogle updatedAt Timestamp 更新日 onRequestSubscriptionApple,onRequestSubscriptionGoogle cancelAt Timestamp? キャンセル日 onRequestSubscriptionApple,onRequestSubscriptionGoogle canceledAt Timestamp? キャンセルした日 onRequestSubscriptionApple,onRequestSubscriptionGoogle currentPeriodStart Timestamp サブスク開始日 onRequestSubscriptionApple,onRequestSubscriptionGoogle currentPeriodEnd Timestamp サブスク終了日 onRequestSubscriptionApple,onRequestSubscriptionGoogle endedAt Timestamp? サブスクを終了した日 onRequestSubscriptionApple,onRequestSubscriptionGoogle trialStart Timestamp? お試し開始日 onRequestSubscriptionApple,onRequestSubscriptionGoogle trialEnd Timestamp? お試し終了日 onRequestSubscriptionApple,onRequestSubscriptionGoogle price Number 金額 onRequestSubscriptionApple,onRequestSubscriptionGoogle productId String 商品ID onRequestSubscriptionApple,onRequestSubscriptionGoogle status String enum: active, canceled, incomplete, incompleteExpired, pastDue, trialing, unpaid onRequestSubscriptionApple,onRequestSubscriptionGoogle ただ、status周りはストアごとにもあったりするのでもう少し精査の必要性あり CloudFunctions設計 onRequestSubscriptionApple / onRequestSubscriptionGoogle Requestで実装。 Apple/Googleからsubscriptionの更新があった時に発火されるwebhook先のURL。 受け取ったレシートを検証し、subscriptionsやbillingTransactionsレコードを管理する また、userのclamsもサブスクの状態を見て更新します。 onCreatePlaylistMovie, onDeletePlaylistMovie ユーザがプレイリストを追加したり、削除した時に発火。プレイリスト数を増減させる。 ユースケースを考える 動画のストリーミングについて 動画はcloud storageに保管すればストリーミング再生ができそうな雰囲気は醸し出してます。 検索について 検索条件がたくさんあるので全部algoriaにお任せです。 ただ、検索キーとして、movieのsurfersやfilmersなどで使ってる名前を別途配列として定義してしまえばalgoriaを使わなくても検索をすることは可能です。(フィールドは検索用にたくさん増えてはしまいますが、algoria使わなくていいので実装コストは下がる) ただ、最近extensionも出たので、algoriaの実装難易度も大分下がりました。 まとめ ユーザ同士の交流といった機能はないので、実装コストは大きく削れそうです。 そう考えるとユーザ交流させるサービスのコストって結構高いんだなぁと改めて感じました。 私はスノボが好きなのですが、いずれNobody Snowみたいな雪山版のイケてるサービスも作れたらいいなと密かに思ったりしています。 最後に、ワンナイト人狼オンラインというゲームを作ってます!よかったら遊んでね! 他にもCameconやOffchaといったサービスも作ってるのでよかったら使ってね! また、チームビルディングや技術顧問、Firebaseの設計やアドバイスといったお話も受け付けてますので御用の方は弊社までお問い合わせください。