LoginSignup
7
6

More than 5 years have passed since last update.

Google Sign-InのDelegateをRxSwift対応させてどこからでも簡単に呼び出す

Last updated at Posted at 2018-09-20

開発中のプロジェクトでGoogle APIを使うことになり、アカウント管理を含めGoogleサインインを導入しました。
ただ、その際に公式のframeworkのまま使おうとした場合に、今回の要件上イケてないと感じた部分がいくつかあったため、
RxSwiftを用いた拡張フレームワークを作成しました。

今回の実装のサンプルコードはオープンソースで公開しました。

前提条件

  • Swift 4.2
  • Xcode 10.0
  • 公式ドキュメント通りに動かすことはできてる前提( Cloud Console上の設定、ライブラリ導入など)
導入方法で参考にしたもの

イケてないポイント

今回取り組んだイケてないポイントは次の2点です。

  1. DelegateじゃなくRxで非同期に処理を扱いたい
  2. signInSilently() できない!?(扱いづらい)

1. DelegateじゃなくRxで非同期に処理を扱いたい

GoogleSignIn FrameworkはDelegateを利用し、ログインアクションの結果を受け取る仕様になっています。
しかし今回のアプリではRxSwiftを使用しMVVMで実装を行なっていたため、できればログインアクションをリアクティブに
行い、関連処理をVM内のメソッドチェーンでコンパクトに処理したいと思いました。

RxSwiftにはDelegateメソッドを扱うためのDelegateProxyというものがあったので今回 @tako3a の記事
RxSwiftのGithub上に上がっていたIssueを参考にGIDSignInクラスのRxエクステンションを実装しました。

GIDSignInDelegateのDelegateProxyを実装

GIDSignIn+Rx.swift
 import GoogleSignIn
 import RxSwift
 import RxCocoa

 class RxGIDSignInDelegateProxy: DelegateProxy<GIDSignIn, GIDSignInDelegate>,GIDSignInDelegate  {
     public weak private(set) var gidSignIn: GIDSignIn?
     var signInSubject = PublishSubject<GIDGoogleUser>()

     init(gidSignIn: ParentObject) {
         self.gidSignIn = gidSignIn
         super.init(parentObject: gidSignIn, delegateProxy: RxGIDSignInDelegateProxy.self)
     }

     func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
         if let u = user {
             signInSubject.on(.next(u))
         } else if let e = error {
             signInSubject.on(.error(e))
         }
         _forwardToDelegate?.sign(signIn, didSignInFor:user, withError: error)
     } 

     deinit {
         signInSubject.on(.completed)
     }
 }

 extension RxGIDSignInDelegateProxy :DelegateProxyType {
     static func registerKnownImplementations() {
         register { RxGIDSignInDelegateProxy(gidSignIn: $0) }
     }

     static func currentDelegate(for object: GIDSignIn) -> GIDSignInDelegate? {
         return object.delegate
     }

     static func setCurrentDelegate(_ delegate: GIDSignInDelegate?, to object: GIDSignIn) {
         object.delegate = delegate
     }
 }

 extension Reactive where Base: GIDSignIn {
     public var delegate: DelegateProxy<GIDSignIn, GIDSignInDelegate> {
         return self.gidSignInDelegate
     }

     var signIn: Observable<GIDGoogleUser> {
         let proxy = self.gidSignInDelegate
         proxy.signInSubject = PublishSubject<GIDGoogleUser>()
         return proxy.signInSubject
             .asObservable()
             .do(onSubscribed: {
                 proxy.gidSignIn?.signIn()
             })
             .take(1)
             .asObservable()
     }
 }

細かい説明は省きますが、DelegateProxyとしてRxGIDSignInDelegateProxyを定義し、
signInSubject:PublishSubject<GIDGoogleUser>を変数として持たせます。
Reactive Extention上で
signIn: Observable<GIDGoogleUser>
としてそれをラップした上でdoメソッドとしてGIDSignInクラスのsignIn()を呼んでいます。
利用する側としては

LoginVIewModel.swift
let login = GIDSignIn.sharedInstance().rx.login
                .flatMap({ (user:GIDUser) in
                  //GIDUserを使ってなんか処理
                })
                .share(replay: 1)

のようにDelegateの実装なしに晴れてメソッドチェーンを繋げていくことができます!

2. signInSilently() できない!?(扱いづらい)

無事にスマートなログイン処理が実装できたのですが、この後も問題が発覚しました。
ログインを終えたユーザにはGIDSignInクラスにはsignInSilentlyというメソッドが用意されています。

// Attempts to sign in a previously authenticated user without interaction. The delegate will be
// called at the end of this process indicating success or failure.
- (void)signInSilently;

ところが下記のコードではユーザ情報が取得できませんでした。

AppDelegate.swift
 import UIKit
 import GoogleSignIn

 @UIApplicationMain
 class AppDelegate: UIResponder, UIApplicationDelegate  {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions 
                                launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
         GIDSignIn.sharedInstance().clientID = "your client ID"
         GIDSignIn.sharedInstance().scopes = ["your scopes"]

         GIDSignIn.sharedInstance().signInSilently()

         //ログインを1度した後再度起動するとtrueが返ってくる
         if GIDSignIn.sharedInstance().hasAuthInKeychain() {
             print(GIDSignIn.sharedInstance()?.currentUser)
             //なぜか常にnil。。
         }

         return true
     }

currentUserを呼ぶタイミングを遅延させたりいろいろと試してみましたが常にnilが返ってきてしまいます。
この現象は下記のStackOverFlowでも報告されていました。
GIDSignIn.sharedInstance().currentUser nil when relaunching app

解決策

類似のStackOverFlowを探っていくと以下の情報を見つけました
Swift+GIDSignIn - hasAuthInKeyChain returns true but currentUser is still nil after calling signInSilently

I had a similar issue. First I thought it was a timing issue. By invoking signIn.signInSilently() after fully loading the root viewController it did work. I am now 99% sure I solved it by executing signIn.signInSilently() on the main thread through

どうやら メインスレッドで動作させた方が良さそうだと言っています。
また、 仕様で書かれている通りDelegateメソッドで結果を受け取った方が確実だと考えました。
ということで、signin()メソッドと同様に、ViewController上からRxエクステンションとして実装しました。

GIDSignInDelegateのDelegateProxyを実装

GIDSignIn+Rx.swift

 extension Reactive where Base: GIDSignIn {
     public var delegate: DelegateProxy<GIDSignIn, GIDSignInDelegate> {
         return self.gidSignInDelegate
     }

     var signIn: Observable<GIDGoogleUser> {
         let proxy = self.gidSignInDelegate
         proxy.signInSubject = PublishSubject<GIDGoogleUser>()
         return proxy.signInSubject
             .asObservable()
             .do(onSubscribed: {
                 proxy.gidSignIn?.signIn()
             })
             .take(1)
             .asObservable()
     }

     //下記を追加実装
     var signInSilent: Observable<GIDGoogleUser> {
        let proxy = self.gidSignInDelegate
        proxy.signInSubject = PublishSubject<GIDGoogleUser>()
        return proxy.signInSubject
            .asObservable()
            .do(onSubscribed: {
                proxy.gidSignIn?.signInSilently()
            })
            .take(1)
            .asObservable()
    }

 }

これでユーザ情報が必要な場面でsignIn()と同様に呼び出すことでGIDUserを取得することができるようになりました。

今回の実装のサンプルコード
https://github.com/kazumanagano/GIDSignIn-Rx

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6