본문 바로가기

개발

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

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의 장점:

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