개발

iOS Combine으로 로그인 로직 만들기

한번사는인생~키야 2024. 7. 10. 12:29
728x90

iOS Combine을 사용한 로그인 로직 구현

Combine 프레임워크를 사용하여 iOS 앱에서 효율적이고 반응형 로그인 로직을 구현하는 방법을 살펴보겠습니다.

1. 로그인 양식 UI 구성

  • 이메일 및 비밀번호 입력 필드를 포함하는 UI를 구성합니다.
  • 각 필드에 대한 입력 유효성 검사 로직을 구현합니다.

2. Combine을 사용한 입력 스트림 처리

  • 각 입력 필드에서 CurrentValueSubject를 사용하여 입력 값을 Observable로 변환합니다.
  • map연산자를 사용하여 입력 값을 필요한 형식으로 변환합니다.
  • validate()함수를 사용하여 입력 값의 유효성을 검사하고 오류 메시지를 생성합니다.

3. Combine을 사용한 로그인 로직 구현

  • combineLatest 연산자를 사용하여 이메일 및 비밀번호 Observable을 결합합니다.
  • flatMap 연산자를 사용하여 로그인 API 호출을 수행합니다.
  • decode연산자를 사용하여 API 응답 데이터를 디코딩합니다.
  • subscribe메서드를 사용하여 응답 처리 및 결과 UI 업데이트를 수행합니다.

예시 코드

import SwiftUI

import Combine



struct LoginView: View {

    @State private var email = ""

    @State private var password = ""

    @State private var errorMessage = ""

    @State private var isLoading = false

    @State private var isLoggedIn = false



    private let disposeBag = DisposeBag()



    var body: some View {

        VStack {

            TextField("이메일", text: $email)

                .padding()



            SecureField("비밀번호", text: $password)

                .padding()



            Button("로그인") {

                isLoading = true



                let input = Publishers.CombineLatest(

                    emailPublisher,

                    passwordPublisher

                )

                .map { email, password in

                    (email, password)

                }

                .validate()

                .flatMap { userData in

                    loginAPI(userData: userData)

                }

                .decode(type: LoginResponse.self)

                .map { response in

                    response.isSuccess

                }

                .subscribe(on: DispatchQueue.main) { [weak self] success in

                    guard let self = self else { return }



                    self->isLoading = false



                    if success {

                        self->isLoggedIn = true

                    } else {

                        self->errorMessage = "로그인에 실패했습니다."

                    }

                }

            }

            .padding()



            if isLoggedIn {

                Text("로그인되었습니다.")

            } else if !errorMessage.isEmpty {

                Text(errorMessage)

                    .foregroundColor(.red)

            }



            if isLoading {

                ProgressView()

            }

        }

        .padding()

    }



    private var emailPublisher: AnyPublisher<String, Never> {

        CurrentValueSubject(email).eraseToAnyPublisher()

    }



    private var passwordPublisher: AnyPublisher<String, Never> {

        CurrentValueSubject(password).eraseToAnyPublisher()

    }

}



private struct LoginResponse: Decodable {

    let isSuccess: Bool

}



private func loginAPI(userData: (String, String)) -> AnyPublisher<LoginResponse, Error> {

    // 실제 API 호출 코드를 작성합니다.

    return Just((isSuccess: true)).setFailureType(to: Error.self).eraseToAnyPublisher()

}



private extension Publishers {

    static func validate<T: Combine.CombinePublisher>(

        _ publisher: T,

        errorPrefix: String = "",

        validations: [(_ value: T.Value) -> String?] = []

    ) -> AnyPublisher<T.Value, Error> {

        publisher

            .flatMap { value in

                validations.reduce(Optional.none) { result, validation in

                    result.orElse(validation(value)) {

                        Error.init(message: errorPrefix + validation(value)!)

                    }

                }

            }

            .eraseToAnyPublisher()

    }

}

 

Combine의 장점:

  • 비동기 작업 처리를 간편하게 수행할 수 있습니다.
  • 코드가 명확하고 읽기 쉽습니다.
  • 오류 처리가 용이합니다.