Google I/O 2022でFirebase Apple SDK 9.0.0の発表、リリースがされました。
メジャーアップデートにより正式にSwiftへ対応したとの事で色々便利になっているらしいのでまずはよく使う認証機能にあたる「Firebase Authentication」を試してみました。
概要:新しくなったFirebase
5/11から開催かれたGoogle I/O 2022でFirebaseについての発表、説明がありました。
詳細を知りたい方は以下動画と記事を参照ください。
今回はこの中でもFirebaseがSwift対応したことによってasync/awaitで処理する事が可能になりました!
早速Firebase Authenticationを試してみました!
前提知識
この記事を読む対象読者としてFirebase Authenticationを知っていて尚且つSwiftでのasync/awaitについてザックリと理解している人を想定していますが、もしそうでなければ以下参考にしていただければと思います。
Firebase Authenticationとは
簡単に言うと認証機能をFirebase (クラウド)側でいい感じにしてくれるmBaaS(Mobile backend as a service)です。
詳しくは公式のコチラの記事をご覧ください。
Firebase Authentication | Firebase Documentation
Firebase Apple SDKの導入方法
今回のFirebaseのメジャーアップデートによりFirebase iOS SDKからFirebase Apple SDKと呼び方が変わっていますが公式の導入方法ではfirebase-ios-sdkと表示されています。
この記事ではFirebase Apple SDKで統一して表示させて頂きますのでご了承ください。
Release Firebase Apple 9.0.0 · firebase/firebase-ios-sdk · GitHub
こちらリリースノート
またXcodeの最小必要バージョンは13.3.1となっていますのでご注意を。
導入方法については公式からとても丁寧に記載されているので以下参照ください。
余談ですがSwiftUI+SPMでの導入の場合、AppDelegateを自作しないといけなかったのですが以下のようにAppDelegateを書かなくても導入する事ができます。
import SwiftUI import Firebase @main struct NewFirebaseAuthenticationDemoApp: App { init() { FirebaseApp.configure() } var body: some Scene { WindowGroup { ContentView() } } }
async/awaitについて
WWDC 2021で発表、リリースがされた非同期処理に関する機能です。
詳しくはコチラの動画と
以下の2記事で概要から使用方法まで網羅する事ができると思いますので是非詳しく知りたい方はご参照ください。
実践:Firebase Apple SDK 9.0.0でのFirebase Authenticationの使用方法
前置きが少し長くなってしまいましたが、実践について以下記載していきたいと思います。
環境
- Xcode: 13.3.1
- Firebase Apple SDK: 9.0.0
実践
まずはSign inから試してみたいと思います。 以前はコールバック関数での使用となっていましたのでこんな感じで使用していました。
func signin(email: String, password: String, failure:@escaping (String) -> Void, success:@escaping () -> Void) { Auth.auth().signIn(withEmail: email, password: password) { result, error in if error == nil { success() } else { if let errorCode = AuthErrorCode(rawValue: error!._code) { switch errorCode { case .invalidEmail: failure(SigninError.InvalidEmail.title) case .weakPassword: failure(SigninError.WrongPassword.title) case .wrongPassword: failure(SigninError.WrongPassword.title) case .userNotFound: failure(SigninError.UserDisabled.title) case .networkError: failure(SigninError.NetworkError.title) default: failure(SigninError.unknown.title) } } } } }
withCheckedThrowingContinuationを使用する事でコールバックをラップしてasync / awaitに対応した書き方ができます。 こんな感じです。
func signin(email: String, password: String) async throws -> Void { return try await withCheckedThrowingContinuation{ continuation in Auth.auth().signIn(withEmail: email, password: password) { user, error in if error == nil { // サインインに成功した時の処理 continuation.resume(returning: print("User ID:" + user!.user.uid)) } else { // サインインに失敗した時の処理 if let errorCode = AuthErrorCode(rawValue: error!._code) { switch errorCode { case .invalidEmail: continuation.resume(throwing: SigninError.InvalidEmail) case .weakPassword: continuation.resume(throwing: SigninError.WrongPassword) case .wrongPassword: continuation.resume(throwing: SigninError.WrongPassword) case .userNotFound: continuation.resume(throwing: SigninError.UserDisabled) case .networkError: continuation.resume(throwing: SigninError.NetworkError) default: continuation.resume(throwing: SigninError.unknown) } } } } } }
では実際新しくなったAuthenticationを試してみます。
func signInAndGetUid(email: String, password: String) async -> String { do { let result = try await Auth.auth().signIn(withEmail: email, password: password) let uid = result.user.uid return uid } catch { let errorCode = AuthErrorCode.Code(rawValue: error._code) switch errorCode { case .networkError: return AuthError.networkError.title case .weakPassword: return AuthError.weakPassword.title case .wrongPassword: return AuthError.wrongPassword.title case .userNotFound: return AuthError.userNotFound.title default: return AuthError.unknown.title } } }
do-try-catchで書けるようになり、非常にみやすくなった印象です。 またthrowsを使用すればエラーハンドリングも別で記載する事ができるので更にシンプルに書く事ができます。
func signin(email: String, password: String) async throws -> String { let result = try await Auth.auth().signIn(withEmail: email, password: password) let uid = result.user.uid return uid }
たったこれだけでサインインに成功すればuidを吐き出し、失敗すればthrowsを投げてくれます。
変わった点
上記のコード通り、まず非常にシンプルに書く事ができるようになりました。 またdo-try-catchで書けるようになり、非常に見やすくなったのもすごく良いです。 またthrowsを使用すれば更にシンプルに書く事ができ、functionの定義からたったの5行で実装する事ができます。
一通り試してみた
更にサインアップやサインアウト、パスワードの再設定とAuthenticationで必要になりそうな実装を一通り試してみました。 ここでは認証結果をStringで吐き出すサンプルとして記載しています。
- enum エラーに対して何のエラーかStringを吐き出してくれるように定義します。
public enum AuthError: Error { // ネットワークエラー case networkError // パスワードが条件より脆弱であることを示します。 case weakPassword // ユーザーが間違ったパスワードでログインしようとしたことを示します。 case wrongPassword // ユーザーのアカウントが無効になっていることを示します。 case userNotFound // メールアドレスの形式が正しくないことを示します。 case invalidEmail // 既に登録されているメールアドレス case emailAlreadyInUse // 不明なエラー case unknown //エラーによって表示する文字を定義 var title: String { switch self { case .networkError: return "通信エラーです。" case .weakPassword: return "パスワードが脆弱です。" case .wrongPassword: return "メールアドレス、もしくはパスワードが違います。" case .userNotFound: return "アカウントがありません。" case .invalidEmail: return "正しくないメールアドレスの形式です。" case .emailAlreadyInUse: return "既に登録されているメールアドレスです。" case .unknown: return "エラーが起きました。" } } }
- サインイン 上記にもありますが改めて。
func signInAndGetUid(email: String, password: String) async -> String { do { let result = try await Auth.auth().signIn(withEmail: email, password: password) let uid = result.user.uid return uid } catch { let errorCode = AuthErrorCode.Code(rawValue: error._code) switch errorCode { case .networkError: return AuthError.networkError.title case .weakPassword: return AuthError.weakPassword.title case .wrongPassword: return AuthError.wrongPassword.title case .userNotFound: return AuthError.userNotFound.title default: return AuthError.unknown.title } } }
- サインアップ
func signUpAndGetUid(email: String, password: String) async -> String { do { let result = try await Auth.auth().createUser(withEmail: email, password: password) let uid = result.user.uid return uid } catch { let errorCode = AuthErrorCode.Code(rawValue: error._code) switch errorCode { case .networkError: return AuthError.networkError.title case .weakPassword: return AuthError.weakPassword.title case .emailAlreadyInUse: return AuthError.emailAlreadyInUse.title default: return AuthError.unknown.title } } }
- サインアウト
func signOut() async -> String { do { try Auth.auth().signOut() return "サインアウトしました" } catch { let errorCode = AuthErrorCode.Code(rawValue: error._code) switch errorCode { case .networkError: return AuthError.networkError.title default: return AuthError.unknown.title } } }
- パスワードの再設定
func passwordReset(email: String, password: String) async -> String { do { try await Auth.auth().sendPasswordReset(withEmail: email) return "パスワードを再設定するメールを送信しました。" } catch { let errorCode = AuthErrorCode.Code(rawValue: error._code) switch errorCode { case .networkError: return AuthError.networkError.title case .userNotFound: return AuthError.userNotFound.title case .invalidEmail: return AuthError.invalidEmail.title default: return AuthError.unknown.title } } }
所感
サインアウトは以前からですが、その他もdo-try-catchで書けるようになったので非常に書きやすさと見やすくなった印象です。 またthrowsを使用する事でロジックに注力したり、認証してそのままFireStoreにuidを投げる事がシンプルに書けそうで非常に良いメジャーアップデートだと感じました。 次回は是非FireStoreも試してみたいと思いますので次回もお楽しみに!