Facebook, Twitter, Line の SNS 連携を取りまとめる LoginModel を作る
Swift2.2, Xcode7
最近は SNS ログインが増えてきたので Facebook, Twitter, Line の各 SNS のログイン処理をプロトコルを使って共通化して書く方法をご紹介します。
- Swift でのポリモーフィズムのような実装を Protocol で行う
- 各 SNS の認証処理を行う Model を作成する
- 各 SNSログインの処理を管理する LoginModel を作成する
Facebook, Twitter, Line の SDK をアプリへの設置が完了していることを前提に書いていますので、SDK の導入方法は各公式サイトを参考にしてください。Facebook と Twitter は Acounts.Framework ではなく、SDK の利用例です。
1. Swift でのポリモーフィズムのような実装を Protocol で行う
SNS共通で行う処理を定義するプロトコルを作成します。今回は以下のようにインターフェースを設計しました。
SNSAuthenticateProtocol.swift
/** SNS連携を管理する Model で採用するプロトコル */ protocol SNSAuthenticateProtocol { // 各 SNS 固有の uid を保持する var snsID: String { get } // 各 SNS の認証で取得した token を保持する var snsToken: String { get } // 各 SNS サービスの認証結果を処理する func authenticate(fromViewController: UIViewController) // 各 SNS サービスのユーザー情報を取得する func fetchUserData() }
今回の各 SNS を enum で定義します。
LoginType.swift
enum LoginType { case Line case Facebook case Twitter var snsModel: SNSAuthenticateProtocol { switch self { case .Line: return LineModel.sharedInstance case .Facebook: return FacebookModel.sharedInstance case .Twitter: return TwitterModel.sharedInstance } } var snsID: String { return self.snsModel.snsID } var snsToken: String { return self.snsModel.snsToken } }
SNSAuthenticateProtocol は FacebookModel, TwitterModel, LineModel で採用します。採用したクラスでは snsID や authenticate が実装されることが保証されるので、以下のような感じで SNS の type をそこまで意識せずに認証処理を実行することができます。
func request(loginType: LoginType, fromViewController: UIViewController) { self.loginType = loginType loginType.snsModel.authenticate(fromViewController) } func fetchUserData(loginType: LoginType) { loginType.snsModel.fetchUserData() }
2. 各 SNS の認証処理を行う Model を作成する
各SNS の認証処理をそれぞれ実装します。その際 SNSAuthenticateProtocol を採用します。
FacebookModel.swift
import FBSDKLoginKit class FacebookModel: NSObject, SNSAuthenticateProtocol { static let sharedInstance = FacebookModel() var snsID = "" var snsToken = "" // MARK: SNSAuthenticateProtocol func authenticate(fromViewController:UIViewController) { guard FBSDKAccessToken.currentAccessToken() == nil else { self.setLoginInformation(FBSDKAccessToken.currentAccessToken()) // ログイン済みの時の処理とか書く return } let manager = FBSDKLoginManager() manager.loginBehavior = .SystemAccount manager.logInWithReadPermissions(["public_profile"], fromViewController: fromViewController) { (result: FBSDKLoginManagerLoginResult?, error: NSError?) -> Void in guard let loginResult = result else { return } guard error == nil else { return } if loginResult.isCancelled { return } self.setLoginInformation(loginResult.token) // ログイン後の処理とか書く } } func fetchUserData() { let graphRequest = FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "name"]) graphRequest.startWithCompletionHandler({ (connection, result: AnyObject?, error) -> Void in guard error == nil else { return } guard let user = result else { return } // ユーザー情報取得後の処理とか書く }) } // MARK: private method private func setLoginInformation(accessToken: FBSDKAccessToken) { self.snsID = accessToken.userID self.snsToken = accessToken.tokenString } }
TwitterModel.swift
import TwitterKit class TwitterModel: NSObject, SNSAuthenticateProtocol { static let sharedInstance = TwitterModel() var snsID = "" var snsToken = "" var loginSession: TWTRSession? // MARK: SNSAuthenticateProtocol func authenticate(fromViewController: UIViewController) { Twitter.sharedInstance().logInWithCompletion { (session: TWTRSession?, error: NSError?) in if let error = error { if error.code == TWTRLogInErrorCode.Canceled.rawValue { return } return } guard let user = session else { return } self.loginSession = session self.setLoginInformation(user) // ログイン後の処理を書く } } func fetchUserData() { // ユーザー情報の取得処理とか書く。 // Facebook と違って Twitter はユーザー情報が session の中に含まれているので、 // 再リクエストの必要はない。 self.loginSession にユーザー情報は入っている。 print(self.loginSession) } private func setLoginInformation(user: TWTRSession) { self.snsID = user.userID self.snsToken = user.authToken } }
LineModel.swift
class LineModel: NSObject, SNSAuthenticateProtocol { static let sharedInstance = LineModel() var snsID = "" var snsToken = "" var loginSession: AnyObject? private let lineAdapter = LineAdapter.adapterWithConfigFile() override init() { super.init() NSNotificationCenter.defaultCenter().addObserver( self, selector: #selector(self.lineAuthorizationDidChange(_:)), name: LineAdapterAuthorizationDidChangeNotification, object: nil) } deinit { NSNotificationCenter.defaultCenter().removeObserver(self) } // MARK: SNSAuthenticateProtocol func authenticate(fromViewController: UIViewController) { guard self.lineAdapter.canAuthorizeUsingLineApp else { return } // 既に認証済みだったらログインを行う if self.lineAdapter.authorized { self.login() } else { self.lineAdapter.authorize() } } func fetchUserData() { // ユーザー情報の取得処理とか書く。 // Facebook と違って Line はユーザー情報が session の中に含まれているので、 // 再リクエストの必要はない。 self.loginSession にユーザー情報は入っている。 print(self.loginSession) } /** Line認証後の判定処理 - parameter notification:通知オブジェクト */ func lineAuthorizationDidChange(notification: NSNotification) { guard notification.userInfo?["error"] == nil else { return } guard self.lineAdapter.authorized else { return } self.login() } // private method private func login() { self.lineAdapter.getLineApiClient().getMyProfileWithResultBlock {[unowned self] (session, error) -> Void in guard error == nil else { return } self.loginSession = session self.setLoginInformation(session) // ログイン処理とか書く } } private func setLoginInformation(session: AnyObject) { guard let id = session["mid"] as? String else { return } guard let token = self.lineAdapter.getLineApiClient().accessToken else { return } self.snsID = id self.snsToken = token } }
3. 各 SNSログインの処理を管理する LoginModel を作成する
LoginModel.swift
class LoginModel: NSObject { static let sharedInstance = LoginModel() var loginType: LoginType? func request(loginType: LoginType, fromViewController: UIViewController) { self.loginType = loginType loginType.snsModel.authenticate(fromViewController) } func fetchUserData(loginType: LoginType) { loginType.snsModel.fetchUserData() } // ログイン成功を受けて self.fetchUserData(loginType) を実行したりする。 }
これで準備が完了です。実際にログインを行う LoginViewController の実装です。
LoginViewController.swift
class LoginViewController: UIViewController { ... 省略 // MARK: IBAction @IBAction func lineLogin() { LoginModel.sharedInstance.request(.Line, fromViewController: self) } @IBAction func facebookLogin() { LoginModel.sharedInstance.request(.Facebook, fromViewController: self) } @IBAction func twitterLogin() { LoginModel.sharedInstance.request(.Twitter, fromViewController: self) } }
スッキリでいい感じですね!