Slug david cover 2

SwiftのEnumを利用してログイン処理を簡略化する

ログイン方法が1つしか用意されていないアプリは、ユーザーの選択肢を制限していて低いレビューに繋がることが多いです。一方で、ログイン方法が複数あるとコードが非常に複雑になってしまいます。 David EastはSwiftのEnumを使うことで、簡単に複雑さを取り除くことができることと、ビューコントローラーやログイン処理のコードをキレイで分かりやすい状態に保つ方法をデモします。 Swiftの第1級オブジェクトであるEnumの強力さと、信頼できる認証フローを構築する方法を学びましょう。


私の名前はDavid Eastです。私はGoogle社のFirebaseチームに所属しているDeveloper Advocateです。私たちはFirebaseで認証に関わる多くの仕事をしています。この記事では、どうやってユーザーと認証を通して信頼を築くか、なぜユーザーと信頼を築きたいか、またどうやってSwiftのEnumを使ってコードで実現するかを伝えたいと思います。

ゾッとする低評価レビュー (00:54)

私の友人たちが、Etchという素晴らしいキーボードアプリを開発しました。それは未だかつて無かったジェスチャーベースのマルチタスクキーボードで、ユーザー同士の素早いコミュニケーションや、グリッド上に単純な形状を描いてさまざまなサービスへのアクセスを可能にします。私はこのアプリのテストを手伝い、すばらしいアプリだと思いました。そして、リリースした週にすぐApp Storeのベスト新着アプリとしてフィーチャーされました。私はきっと評価が5のレビュー付くであろうことを待ちきれませんでした。

そのアプリのレビューは、「楽しくてユニークだが、もう少し手を入れる必要がある」のような良い評価で始まりました。その後、Facebookログインが必須なことに焦点が当てられて、「このアプリは手抜きで、独自ログインや他の選択肢を提示しないインチキな仕様だ」のような評価が1のレビューで荒れ始めました。これらの評価を考慮して、ユーザーの信頼を得るには彼らに選択肢を示す必要があると結論づけました。ユーザーはデベロッパーのようにFacebookログインに頼らないので、このアプリが広く知れ渡らない限りは、ユーザーはこのアプリを信頼するのは難しいでしょう。

例えば、Overcastというポッドキャストアプリはうまくユーザに選択肢を提供しています。最初にアプリを起動した時、そのアプリはあなたの情報を取得しようとはしません。「私はOvercastが初めてです、私は既にアカウントを持っています、私はアカウントが必要ありません」といった選択肢が表示されます。また、なぜアカウントが必要なのかをFAQで説明していたり、読みやすいプライバシーポリシーがあります。このアプリは7000個近くのレビューがあり、その大部分が評価5となっていて、ユーザーの信頼を得るのに成功しています。Etchに関しては、もしメールアドレスでのログインを実装する十分な時間があったら、評価が1のレビューは無かったでしょう。私たちは、選択肢を提供することで信頼を築くことができるのだと学びました。

SwiftのEnum (07:17)

さまざまなSDKを使って、複数のログイン方法を提供するのは難しいことです。しかし、Swiftには複数の選択肢を提供するのに適している、すばらしい概念であるEnumがあります。

記事の更新情報を受け取る

enum LoginProvider {
  case Facebook
  case Email
  case Google
  case Twitter
}

Enumはその型に共通の整数の集合と見なされていました。Objective-CやCでは、これらは単に0、1、2、3と順に関連付けられているだけでした。Swiftはそうではありません。それらを自分で関連付けしない限り、整数値には関連付けされません。それぞれの列挙されているcaseはEnum自体の値と型を持っています。この場合は、型はLoginProviderとなります。そのためにSwiftではEnumは第1���オブジェクトなのです。

Enumのすばらしい4つの特長 (08:12)

1 - Raw Values (08:23)

Raw Valuesはすばらしいです。それぞれのケースに対して値を持たせることができて、その全てが同じ型に準拠しています。

enum LoginProvider: String {
  case Facebook = "facebook"
  case Email = "email"
  case Google = "google"
  case Twitter = "twitter"
}

let provider = LoginProvider.init(rawValue: "email")

この場合、Raw ValueはString型です。それらに一意な文字列を持たせることで、任意の文字列からEnumを初期化出来ます。例えば、emailという文字列を与えると、LoginProviderEnumのEmailが得られます。

2 - Associated Types (08:57)

Raw Valueとして値を持たせられるように、Associated ValueでもEnumに値を持たせられます。しかし、全ての要素で同じString型や数値型である必要はなく、それぞれの要素がそれぞれの型を持てます。

enum LoginProvider {
  case Facebook
  case Email: (String, String)
  case Google
  case Twitter
}

let provider = LoginProvider.Email("[email protected]", "pass")

つまり、Associated ValueはRaw Valueの場合のようにあらかじめ決められた値で初期化されるものではありません。変数なので、自由に値を指定することができます。ソーシャルログインの場合は、それぞれが異なる種類の認証情報を必要とします。しかし、メールアドレスログインの場合だとメールアドレスとパスワードが必要とされるので、LoginProvider.Emailとしてそれらの2つの文字列を保持できます。これらのAssociated Valueはどの型にもなれるので、Raw ValueのようにStringやIntやCharなどの型に制限されていません。

この2つの文字列を扱う代わりに、LoginUserというStructでこれらの2つの文字列を保持して、メールアドレスとパスワードのバリデーションを行う関数を提供できます。

struct LoginUser {
  let email: String
  let password: String
  func isValid() -> Bool {
    return email != "" && password != ""
  }
}

これらの2つの文字列を使うより、LoginUserLoginProvider.EmailのAssociated Valueとして保持する方を好んで使っています。これで下記のようにログインユーザー情報を関連付けられます。

let provider = LoginProvider.Email(user)

それらの値を抜き出すには、Enumに対してswitch文を使います。case文でcase letもしくはcase varを使って取得できます。case文の中で、ユーザー情報にアクセスしてログインさせることができるので、非常にすばらしい機能です。

switch provider {
  case let .Email(user)
    // login!
    break
}

3 - case where (10:43)

Associated Valueに慣れると、case whereが非常に心地よくなります。以前は、保持されているメールアド���スを取得する際にcase letを使っていました。case whereを使えば、先ほど作ったバリデーション用の関数を実行して有効なユーザーかどうか確認することもできます。このように、case文でより詳細な処理が可能となります。

switch provider {
  case let .Email(user) where user.isValid():
    // login!
    break
  case let .Email(user) where !user.isValid():
    // don’t login!
    break
}

それぞれのユーザーが有効か確認出来ます。以前はenum自体が一致するかどうかのみcase文で判別していましたが、これらはより表現豊かなswitchのパターンマッチが可能なので、極めて強力な機能です。

4 - Functions (11:30)

Enumに関数を持たせられるので、SwiftではEnumは第1級オブジェクトということになります。 関数内にてswitch文でselfを使うと、selfとEnumの要素とを判定できます。

enum LoginProvider {
  func login() {
    switch self {
    case let .Email(user) where user.isValid():
      // login!
      break
    }
  }
}

もしあなたが.Facebookを使用していたら、self.Facebookということになります。今回の例だと上記のように.Emailと一致しますが。.Facebookを使用するには、下記のようにLoginProviderをインスタンス化してloginメソッドを呼びましょう。

let provider = LoginProvider.Facebook
provider.login()

// どうやってログインしたことをビューコントローラーへ伝えられるか?

この2行のコードだけで、複数のソーシャルログインをある程度抽象化できます。ログインが非同期処理なために、残念ながらユーザー情報をビューコントローラーに返す構造になってないので、その点について対処する必要があります。複数種類のログインに対応する場合は、異なる非同期のフローに対処することになります。

ログインSDK (12:37)

ソーシャルログイン用SDKは雪の結晶のようです。それぞれは美しいですが、全く同じものはありません。

  • Facebook - ログイン、ユーザー情報取得は非同期実行
  • Google - 2つのデリゲートが必須(そのうち1つはUIに関するものです)。
  • Twitter - アカウント情報取得、ログインは非同期実行
  • Email & Password - 自分自身で実装する

非同期データフローの簡略化 (13:14)

他にログイン処理を提供しているものを探し回る代わりに、独自のフローを定義して順守させましょう。ビューコントローラーを扱う場合は、Swiftらしくないですがデリゲートが良く機能します。

LoginProviderDelegateプロトコルを作成して、簡潔では無いですが良く機能するデリゲートの構文を利用します。ユーザーがログインするか、エラーが発生するかを伝えるメソッドを作成します。ソーシャルログインにおいて、使うべきデリゲートメソッドはもっと多くあるでしょうが、必要な分だけ追加しましょう。

protocol LoginProviderDelegate {
  func loginProvider(loginProvider: LoginProvider, didSucceed user: User)
  func loginProvider(loginProvider: LoginProvider, didError error: NSError)
}

これを機能させるやり方として、デリゲートをEnumで定義してあるlogin(delegate:)関数の引数にします。そしてswitch構文でselfを使い、有効なユーザーの場合はログインを行い、ユーザーオブジェクトを渡し、それがサーバーとの非同期呼び出しの際に返却されます。

成功した場合は、ビューコントローラーにあるデリゲートメソッドを呼びます。それによってビューコントローラーが読み易くなり、ロジックを切り離せます。

class ViewController: UIViewController, LoginProviderDelegate {
  let provider = LoginProvider.Facebook

  @IBAction func loginDidTouch() {
    provider.login(self)
  }

  func loginProvider(loginProvider: LoginProvider, didSucceed: User) {
    print(user)
  }

ビューコントローラーにLoginProviderDelegateを準拠させて、LoginProviderのデフォルト値をFacebookとして定義します。もしタップしたボタンにIBアクションが紐付いていれば、ビューコントローラー自体をデリゲートとして渡します。成功した場合はログイン処理を実行します。これはLoginProviderがどの値でも機能します。その他の方法を気にせずに、全ての値に対してこのフローを従わせられます。

デモ (15:03)

ログインに関する全てのコードが通る独自のフローを定義したデリゲートによって、ログイン用ビューコントローラーを大いに簡素化出来て、複雑なログイン関連のコードをビューコントローラーから切り離せます。他の人が書いた実装の詳細やコールバックを追いかける代わりに、このやり方で綺麗で簡単なモデルの操作に統一しましょう。

結論として、選択肢を提供することでユーザーの信頼を得たかったのですが、Swiftのenumでそれらを簡素化出来るでしょう。ありがとうございました。

Q&A (18:55)

Q: 複数ログインの選択肢を実現する上で、Enumを使うのとプロトコルを使うのをどう比較しましたか?それらの間で何がトレードオフとなりますか?

David: 今回はできるだけSwiftらしくするという実験でした。過去にはログイン処理のラッパーも実装しましたし、プロトコルも使用しました。それらの方法でもビューコントローラーはキレイになるでしょう。しかし、Enumを使うと複数ログインの選択肢が調和するので、非常に上手く機能します。プロトコルはもう一つの実行可能な方法です(常にEnumを使用するわけではなく、プロトコルも良く機能する1つの選択肢です)。

Q: Enumとプロトコルの両方に焦点を当てましたが、この記事での最も重要なメッセージは何ですか? ログインフローを構築するのに、やはりEnumを使用するのを推奨しますか?

David: 両方です。最も重要なメッセージは、Overcastがしたように、様々な認証の種類を提供してユーザーの信頼を得るように考えることです。本当はもっと、SwiftのEnumがどれほど強力で、いかに簡単にその力を発揮できるかを見せたかったです。

Q: 最近ログインフローをEnumで実装して、扱い難くなるほど多くのAssociated Valueに遭遇しましたが、独自オブジェクトを使用するのがコードをキレイにする素晴らしい方法ですね!

David: はい、私も実際に同じ状況になりましたが、独自の型を使うのが実に良い方法でした。

About the content

This content has been published here with the express permission of the author.

David East

David East is a Developer Advocate at Google working on the Firebase & Angular teams. He’s also the co-author of AngularFire2. He takes full advantage of Google’s free coffee perk. ☕️

4 design patterns for a RESTless mobile integration »

close