Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[REFACTOR] Auth관련 VC들 MVVM(Combine)-C으로 변경 (#164) #165

Merged
merged 12 commits into from
Nov 16, 2023

Conversation

kimscastle
Copy link
Contributor

@kimscastle kimscastle commented Nov 16, 2023

[#164] REFACTOR : Auth관련 VC들 MVVM(Combine)-C으로 변경 (#164)

🌱 작업한 내용

1. 페어코딩으로 Auth관련 뷰들 리팩터링 완료(2023.11.14~2023.11.16)

라이온하트 MVVM 원칙

1. ViewController의 Input과 Output의 Failure타입은 모두 Never여야한다.

  • ViewController는 String만 받아서 toast에 띄우기만한다 → 에러는 catch operator를 통해서 Publisher.Just에 에러메시지만 담아서ViewController에 던진다.
    (ViewController에서의 stream은 성공이든 에러든 어떤 형태의 completion도 떠서는 안된다. completion을 받게 되면 stream이 끊어지기 때문)

2. ViewController에서 ViewModel은 init에서는 some으로 받는다

  • 최대한 컴파일타임에서 타입추론이 가능하도록 opaque type을 사용한다

일반적으로 성능, 안전성 및 신뢰성을 위해 "any"(런타임)보다 "some"(컴파일 타임)을 사용하는 것을 선호해야 합니다.

3. Factory에서의 VM과 VC는 Protocol타입으로 반환한다

  • 객체타입이 아닌 추상화된 interface를 바라보게 되어 객체간의 결합도를 낮추고 필요한 메서드만 호출할수있도록 합니다.

4. delegate를 combine으로 대체합니다

  • delegate pattern대신 publisher를 활용해 데이터를 상위 객체에게 넘겨줍니다
하위 객체 GetPregnancyVC

1. Data를 상위객체로 보낼 publisher를 선언합니다
var pregnancyIsValid = PassthroughSubject<(pregnancy: Int, isValid: Bool), Never>()

2. 하위객체에서 특정 값을 publisher에 send해줍니다
func bindInput() {
    pregnancyTextfield.textPublisher
        .sink { [weak self] in self?.pregancyTextfieldDidChanged.send($0) }
        .store(in: &cancelBag)
}
상위 객체 OnboardingVC

let pregnancyViewController = GetPregnancyViewController(viewModel: GetPregnancyViewModelImpl())

1. 상위 객체에서 하위객체의 publisher를 subscribe해서 그값을 상위객체의 publisher에 send해줍니다
func setChildViewController() {
  pregnancyViewController.pregnancyIsValid
      .sink { [weak self] in self?.pregenacy.send($0) }
      .store(in: &cancelBag)
}

5. VM에서 Coordinator관련 stream을 따로만들어서 flow stream을 관리합니다

  • SplashVC의 경우에 Network결과에 따라서 navigation flow가 변경됩니다
  • 아래 예시코드와 같이 navigator를 호출해야하는 경우 navigationSubject에 필요한 타입을 넘겨줍니다
let splashErrorMessage = input.lottiePlayFinished
    .map { [weak self] _ -> (String?, String?) in
            ...생략
            self?.navigationSubject.send(.expired)
    }

func reissueToken(refreshToken: String, accessToken: String) async throws {
    ...생략...
    self.navigationSubject.send(.valid)
}
  • 최종적으로 해당 navigationSubject를 VM내부에서 subscribe하여 navgation flow를 실행해줍니다
navigationSubject
    .receive(on: RunLoop.main)
    .sink { state in
        switch state {
        case .expired:
            self.navigator.checkToken(state: .expired)
        case .valid:
            self.navigator.checkToken(state: .valid)
        }
    }
    .store(in: &cancelBag)

🔥이유🔥

이렇게 결정한 이유는 특정 네트워크 결과에따라 main thread에서 실행되어야할 UI관련 코드가 background thread에서 동작하게 되어 문제가 발생할수있기에 매번 네트워킹 코드 내부에서 navigation호출을 DispatchQueue.main.asnyc{}를 해줬어야했습니다. 하지만 navigationSubject자체를 main thread에서 receive하게되면 navgation관련 flow는 main에서 실행됨을 보장받을수있게됩니다.

6. errorMessage를 보내는 Publisher는 Merge Operator를 활용해서 VC가 하나의 스트림만 바라보도록한다.

  • VM에서 두개의 Publisher가 error string을 보내는 경우 VC가 두개의 publisher를 sink하더라도 한번에 하나씩의 error를 띄우기때문에 merge를 활용해서 VC가 하나의 publisher로 부터 error message를 받도록 합니다.
ViewModel✅
func transform(input: SplashViewModelInput) -> SplashViewModelOutput {
  let mergeSubject = splashErrorMessage
      .merge(with: logoutSubject)
      .eraseToAnyPublisher()
  return SplashViewModelOutput(splashNetworkErrorMessage: mergeSubject)
}
ViewController✅
func bind() {
    ...생략...
    /// error를 하나의 stream에서 받는 구조
    output.splashNetworkErrorMessage
        .sink { errorMessage in
            print(errorMessage)
        }
        .store(in: &cancelBag)
}

📮 관련 이슈

@ffalswo2 ffalswo2 added this to the 🦁2차 Refactor🦁 milestone Nov 16, 2023
@kimscastle kimscastle requested review from ffalswo2 and cchanmi and removed request for ffalswo2 and cchanmi November 16, 2023 06:35
@kimscastle kimscastle assigned ffalswo2 and cchanmi and unassigned ffalswo2 and cchanmi Nov 16, 2023
@kimscastle kimscastle added 🦁민재 민재's 🦁의성 의성's 🦁찬미 찬미's ♻️Refactoring 리펙터링 labels Nov 16, 2023
@kimscastle kimscastle changed the title Refactor/#164 [REFACTOR] Auth관련 VC들 MVVM-C(Combine)으로 변경 (#164) Nov 16, 2023
@kimscastle kimscastle merged commit e620896 into main Nov 16, 2023
@kimscastle kimscastle changed the title [REFACTOR] Auth관련 VC들 MVVM-C(Combine)으로 변경 (#164) [REFACTOR] Auth관련 VC들 MVVM(Combine)-C으로 변경 (#164) Nov 16, 2023
@kimscastle kimscastle deleted the refactor/#164 branch November 19, 2023 02:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
♻️Refactoring 리펙터링 🦁민재 민재's 🦁의성 의성's 🦁찬미 찬미's
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[REFACTOR] Auth관련 ViewController들 MVVM(Combine)-C 리팩터링
3 participants