[TIL] MVC에서 MVVM으로 리팩토링하기

Eden·2025년 2월 1일
0

TIL

목록 보기
110/129
post-thumbnail

1차 MVP를 MVC에서 MVVM으로 리팩토링해보았다.

기존 MVC 코드

🔹 기존 PermissionViewController (MVC)

import UIKit
import SnapKit

class PermissionViewController: UIViewController {

    private let permissionView = PermissionView()
    private let familyControlsManager = FamilyControlsManager() // ✅ 직접 권한 요청을 처리하는 객체

    override func viewDidLoad() {
        super.viewDidLoad()
        view = permissionView
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        navigationController?.navigationBar.isHidden = true
        requestScreenTimePermission() // ✅ 권한 요청을 직접 호출
    }

    private func requestScreenTimePermission() {
        familyControlsManager.requestAuthorization { [weak self] in
            DispatchQueue.main.async {
                self?.navigateToStarList() // ✅ 권한이 허용되면 다음 화면으로 이동
            }
        }
    }

    private func navigateToStarList() {
        let starListViewController = StarListViewController()
        navigationController?.pushViewController(starListViewController, animated: false)
    }
}

❌ 기존 MVC 코드의 문제점

  1. ViewController가 너무 많은 역할을 함

    • requestScreenTimePermission()에서 비즈니스 로직(권한 요청)과 화면 이동을 직접 수행
  2. UI 변경이 있을 때 비즈니스 로직도 영향을 받을 가능성이 큼

    • UI가 변경될 때 requestScreenTimePermission() 내부 로직도 수정해야 할 수도 있음

MVVM 적용 후 코드

🔹 PermissionViewModel.swift (MVVM)

//
//  PermissionViewModel.swift
//  star
//
//  Created by Eden on 1/31/25.
//

import Foundation
import RxSwift
import RxCocoa

final class PermissionViewModel {

    private let familyControlsManager: FamilyControlsManager

    init(familyControlsManager: FamilyControlsManager = FamilyControlsManager()) {
        self.familyControlsManager = familyControlsManager
    }
}

extension PermissionViewModel {

    struct Input {
        let requestPermissionTrigger: Observable<Void>
    }

    struct Output {
        let navigateToStarList: Observable<Void>
    }

    func transform(_ input: Input) -> Output {
        let navigateToStarList = PublishRelay<Void>()

        input.requestPermissionTrigger
            .flatMapLatest { [weak self] _ -> Observable<Bool> in
                guard let self = self else { return Observable.just(false) }
                return self.requestScreenTimePermission()
            }
            .filter { $0 } // ✅ granted가 true일 때만 실행
            .map { _ in } // ✅ Bool → Void 변환
            .bind(to: navigateToStarList)
            .disposed(by: DisposeBag())

        return Output(
            navigateToStarList: navigateToStarList.asObservable()
        )
    }

    private func requestScreenTimePermission() -> Observable<Bool> {
        return Observable.create { observer in
            self.familyControlsManager.requestAuthorization {
                observer.onNext(true)
                observer.onCompleted()
            }
            return Disposables.create()
        }
    }
}

ViewModel이 권한 요청을 처리하고, ViewController는 이를 구독하여 UI를 변경함.

ViewModel에서 requestScreenTimePermission()을 실행하여 권한 요청을 수행함.

ViewController는 Output을 구독하여 화면을 이동하는 역할만 담당함.


🔹 리팩토링 후 PermissionViewController.swift (MVVM)

//
//  PermissionViewController.swift
//  star
//
//  Created by Eden on 1/24/25.
//

import UIKit
import SnapKit
import RxSwift

final class PermissionViewController: UIViewController {

    private let permissionViewModel = PermissionViewModel()
    private let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        bind()
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        navigationController?.navigationBar.isHidden = true
    }

    private func bind() {
        let input = PermissionViewModel.Input(
            requestPermissionTrigger: Observable.just(())
        )

        let output = permissionViewModel.transform(input)

        output.navigateToStarList
            .subscribe(onNext: { [weak self] in
                self?.navigateToStarList()
            })
            .disposed(by: disposeBag)
    }

    func navigateToStarList() {
        let starListViewController = StarListViewController()
        navigationController?.pushViewController(starListViewController, animated: false)
    }
}

ViewController는 UI 업데이트 역할만 수행하고, 비즈니스 로직을 담당하지 않음.

ViewModel의 Output을 구독하여 화면 전환을 수행.


📌 MVC vs MVVM 비교

MVC (Before)MVVM (After)
권한 요청 로직ViewController에서 직접 실행ViewModel에서 관리
ViewController 역할UI 관리 + 권한 요청 처리UI 관리만 담당
비즈니스 로직 분리ViewController에 포함됨ViewModel로 이동하여 재사용 가능
테스트 가능성낮음 (UI 코드와 결합)높음 (ViewModel 단위 테스트 가능)
profile
Frontend🌐 and iOS

0개의 댓글

관련 채용 정보