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()
}
✅ 위 signIn(with:) 함수에서 Future를 사용한 이유
Firebase를 사용한 소셜 로그인(Auth.auth().signIn(with: credential))은 비동기적으로 실행되며, 한 번만 결과를 반환하는 작업
즉, 로그인이 성공하거나 실패한 후 추가로 이벤트가 발생하지 않음
그래서 한 번만 값을 방출하고 자동으로 종료되는 Future가 적절한 선택!.
📌 Firebase signIn()의 문제점
Auth.auth().signIn(with: credential) { authResult, error in
if let error = error {
print("로그인 실패: \(error.localizedDescription)")
} else if let user = authResult?.user {
print("로그인 성공: \(user.email ?? "No Email")")
}
}
✅ 문제점
- signIn() 자체가 결과를 즉시 반환하지 않음
- 대신 비동기 콜백을 통해 결과를 전달함
- Combine에서는 Publisher가 필요한데, signIn()은 Publisher가 아님 → Combine과 연결하기 어려움! 😭
✅ Future를 사용하면?
- 반드시 한 번은 결과를 반환 (promise(.success(user)) 또는 promise(.failure(error)))
- 한 번 값이 방출되면 자동으로 종료됨 (.finished 호출 필요 없음)
- AnyPublisher<User, Error> 타입을 반환 → Combine 체이닝 가능 (.map(), .flatMap() 활용 가능)
✅ Future 사용 vs 사용하지 않을 때의 차이
Future를 사용한 경우 (현재 코드)
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()
}
// MARK: 🌟 사용방법
let cancellable = signIn(with: credential)
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("✅ 로그인 완료")
case .failure(let error):
print("❌ 로그인 실패: \(error.localizedDescription)")
}
}, receiveValue: { user in
print("🎉 로그인된 사용자: \(user.email ?? "No Email")")
})
➡️ Future를 사용했을 때 장점
✅ 코드가 간결: Firebase의 비동기 API를 Combine 스트림으로 변환
✅ 자동 종료: 로그인 결과를 방출한 후 자동으로 종료 (별도로 .finished 호출 필요 없음)
✅ Combine 체이닝 가능: .map(), .flatMap() 등을 사용하여 데이터 변환이 쉬움
🚫 Future 없이 직접 PassthroughSubject를 사용한 경우
class AuthService {
private let signInSubject = PassthroughSubject<User, Error>()
func signIn(with credential: AuthCredential) -> AnyPublisher<User, Error> {
Auth.auth().signIn(with: credential) { authResult, error in
if let error = error {
self.signInSubject.send(completion: .failure(error)) // 에러 방출
} else if let user = authResult?.user {
self.signInSubject.send(user) // 성공 이벤트 방출
self.signInSubject.send(completion: .finished) // 스트림 종료
}
}
return signInSubject.eraseToAnyPublisher()
}
}
// MARK: ⭐ 사용 방법
let authService = AuthService()
let cancellable = authService.signIn(with: credential)
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("✅ 로그인 완료")
case .failure(let error):
print("❌ 로그인 실패: \(error.localizedDescription)")
}
}, receiveValue: { user in
print("🎉 로그인된 사용자: \(user.email ?? "No Email")")
})
❌ Future 없이 PassthroughSubject를 사용했을 때 단점
🚫 직접 subject를 생성해서 관리해야 함
🚫 성공/실패 후 .finished를 직접 호출해야 함
🚫 여러 번 이벤트가 방출될 가능성이 있음 (예상치 못한 버그 발생 가능)
'UIKIT > Firebase' 카테고리의 다른 글
❓ FireStore 내에 회원정보를 불러오는 데 왜 tryMap? (0) | 2025.02.27 |
---|---|
❓ Firebase 로그인 메서드 내 map의 역할 (0) | 2025.02.27 |
🚀 Future를 사용한 방식 vs map(\.user)을 사용한 방식의 차이점 (Future 는 필수인가?) (0) | 2025.02.26 |
🔥 MVVM + Combine을 활용한 소셜 로그인 & 이메일 회원가입 (0) | 2025.02.26 |
🔥 Firebase의 회원가입 (이메일&비밀번호 / 소셜로그인) (0) | 2025.02.26 |