https://explorer89.tistory.com/310
페이스북 로그인 기능 구현
https://developers.facebook.com/apps/?show_reminder=true Facebook business.facebook.comhttps://youtu.be/W8NzdN0h50I?si=fwvW92WfBDj__GZZ https://zoeful-log.tistory.com/131 [Swift] SwiftUI로 Facebook Login 구현하기Facebook 공식문서를 보고 정
explorer89.tistory.com
이전에 작성한 게시글 입니다.
이번에는 Facebook + Firebase 로그인 구현을 Combine을 통해 진행했습니다.
여기서 Appdelegate.swift 내용도 반드시 적어야합니다.
Appdelegate.swift
import UIKit
import CoreData
import FBSDKCoreKit
import FirebaseCore
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Firebase 초기화
FirebaseApp.configure()
// Facebook SDK 초기화
ApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions)
return true
}
func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
// Facebook URL 처리
return ApplicationDelegate.shared.application(app, open: url, options: options)
}
...
Scenedelegate.swift
import UIKit
import FBSDKCoreKit
import FBSDKLoginKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
// Facebook 로그인
// 이 코드는 앱이 시작될 때 SDK를 초기화하며, 로그인이나 공유 작업을 수행할 때
// SDK가 Facebook 네이티브 앱의 로그인과 공유를 처리하도록 합니다.
// 그렇지 않으면 사용자가 Facebook에 로그인한 상태에서만 앱 내 브라우저를 통해 로그인할 수 있습니다.
func scene(_ scene: UIScene,
openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else {
return
}
ApplicationDelegate.shared.application(
UIApplication.shared,
open: url,
sourceApplication: nil,
annotation: [UIApplication.OpenURLOptionsKey.annotation]
)
}
...
AuthenticationManager.swift
import Foundation
import FirebaseAuth
import Combine
final class AuthenticationManager {
static let shared = AuthenticationManager()
private init() {}
/// Facebook 등의 소셜 로그인 처리
func signIn(with credential: AuthCredential) -> AnyPublisher<User, Error> {
return Future { promise in
Auth.auth().signIn(with: credential) { authResult, error in
if let error = error {
promise(.failure(error)) // 에러 처리
} else if let user = authResult?.user {
promise(.success(user)) // 성공
}
}
}
.eraseToAnyPublisher()
}
}
AutheticationViewModel.swift
import Foundation
import FirebaseAuth
import Combine
final class AuthenticationViewModel: ObservableObject {
@Published var user: User?
@Published var error: String?
private var subscription: Set<AnyCancellable> = []
func createUser(with credential: AuthCredential) {
// 에러 초기화 목적
error = nil
AuthenticationManager.shared.signIn(with: credential)
.sink { [weak self] completion in
switch completion {
case .failure(let error):
self?.error = error.localizedDescription
case .finished:
print("회원 가입 성공")
}
} receiveValue: { [weak self] user in
self?.user = user
}
.store(in: &subscription)
}
}
OnboardingViewController.swift
- extension을 통해 페이스북 로그인 메서드 구현
- bindView()를 통해 뷰모델의 상태 변화(user, error)를 UI에 반영하기 위해 구현
import UIKit
import FBSDKLoginKit
import Combine
import FirebaseAuth
class OnboardingViewController: UIViewController {
// MARK: - ViewModel
private let viewModel = AuthenticationViewModel()
private var cancellables: Set<AnyCancellable> = []
// MARK: - UI Components
private let onboardingView: OnboardingView = {
let view = OnboardingView()
return view
}()
// MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemGreen
configureConstraints()
onboardingView.calledFaecbookLoginButton().delegate = self
if let token = AccessToken.current,
!token.isExpired {
dismiss(animated: true)
}
bindView()
}
// MARK: - Layouts
private func configureConstraints() {
view.addSubview(onboardingView)
onboardingView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
onboardingView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
onboardingView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
onboardingView.topAnchor.constraint(equalTo: view.topAnchor),
onboardingView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
// MARK: - Functions
// ViewModel을 연동하는 함수
private func bindView() {
viewModel.$user.sink { [weak self] user in
guard user != nil else { return }
print("로그인 성공: \(user?.displayName ?? "사용자 이름 없음")")
self?.dismiss(animated: true) // 로그인 성공 후 화면 닫기
}
.store(in: &cancellables)
viewModel.$error.sink { [weak self] error in
guard let error = error else { return }
print("로그인 실패: \(error)")
self?.showErrorAlert(message: error)
}
.store(in: &cancellables)
}
// 에러를 나타내는 경고창함수
private func showErrorAlert(message: String) {
let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default))
present(alert, animated: true)
}
}
// MARK: - Extensions
/// facebook 로그인 구현
extension OnboardingViewController: LoginButtonDelegate {
func loginButton(_ loginButton: FBSDKLoginKit.FBLoginButton, didCompleteWith result: FBSDKLoginKit.LoginManagerLoginResult?, error: (any Error)?) {
if let error = error {
print("Facebook 로그인 실패: \(error.localizedDescription)")
return
}
// Facebook 로그인 성공 시 Firebase 인증 처리
guard let accessToken = AccessToken.current else { return }
let credential = FacebookAuthProvider.credential(withAccessToken: accessToken.tokenString)
viewModel.createUser(with: credential)
}
func loginButtonDidLogOut(_ loginButton: FBSDKLoginKit.FBLoginButton) {
print("Facebook 로그아웃 성공")
}
}
왜 콜백 함수와 Combine을 쓸 때 Future - Promise를 써야 하는가?
func signIn(with credential: AuthCredential) -> AnyPublisher<User, Error> {
return Future { promise in
Auth.auth().signIn(with: credential) { authResult, error in
if let error = error {
promise(.failure(error)) // 에러 처리
} else if let user = authResult?.user {
promise(.success(user)) // 성공
}
}
}
.eraseToAnyPublisher()
}
Combine은 비동기 작업을 처리하고 결과를 스트림으로 관리할 수 있는 강력한 도구입니다. 하지만 기존의 비동기 작업 방식인 콜백 기반 코드를 Combine으로 변환하려면, 콜백을 Publisher로 감싸야 합니다. 이때, Future가 적합합니다.
- 콜백의 문제점:
- 콜백은 단발성입니다. 비동기 작업의 성공/실패를 처리하지만, 이를 Combine의 Publisher 스트림으로 쉽게 통합하기 어렵습니다.
- 코드의 가독성이 떨어지고 관리가 어렵습니다.
- Future의 역할:
- Future는 단일 비동기 작업의 결과를 Publisher로 변환합니다.
- 작업이 완료되면 Promise를 통해 성공(.success) 또는 실패(.failure) 상태를 전달할 수 있습니다.
- Combine 기반의 작업 체인에 자연스럽게 통합할 수 있습니다.
Future - Promise 코드 설명
- Future 정의:
- Future는 하나의 결과를 비동기적으로 반환하는 Combine의 Publisher입니다.
- Future는 Promise를 통해 작업 완료 시 결과를 전달받습니다.
- Promise 사용:
- Promise는 Future의 결과를 설정하는 클로저입니다.
- 성공 상태를 전달하려면 .success(value)를, 실패 상태를 전달하려면 .failure(error)를 호출합니다.
- 결과 구독 및 사용:
- Future를 반환하는 메서드는 Combine에서 체이닝하거나 구독할 수 있습니다.
'Swift' 카테고리의 다른 글
ViewModel (1) | 2025.01.08 |
---|---|
setCustomSpacing(_:after:)의 역할 (0) | 2025.01.05 |
UICollectionViewCompositionalLayout 관련 데이터 소스 관리 (0) | 2025.01.03 |
async / await 사용해보기 (0) | 2025.01.02 |
async/await란? (0) | 2024.12.18 |