본문 바로가기

UIKIT/Firebase

🤔 소셜 미디어 로그인 방법에서 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()
}


✅ 위 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를 사용하면?

  1. 반드시 한 번은 결과를 반환 (promise(.success(user)) 또는 promise(.failure(error)))
  2. 한 번 값이 방출되면 자동으로 종료됨 (.finished 호출 필요 없음)
  3. 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를 직접 호출해야 함
🚫 여러 번 이벤트가 방출될 가능성이 있음 (예상치 못한 버그 발생 가능)