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] Coordinator Pattern 적용 (#139) #140

Merged
merged 10 commits into from
Oct 14, 2023

Conversation

kimscastle
Copy link
Contributor

@kimscastle kimscastle commented Oct 14, 2023

[#139] REFACTOR : Coordinator Pattern 적용

🌱 작업한 내용

  • 화면전환 로직을 담당하는 coordinator pattern을 도입했습니다

🌱 PR Point

Why? - 코디네이터 패턴을 도입한 이유

Massive VC의 가장 큰 문제 중 하나는 flow logic 및 business logic이 얽혀있다는 것이다!
우리가 UI를 붙여서 쓰고있는 여러 객체들은 View자체의 역할을 해야하는데(여기서 view자체의 역할은 단순히 model을 보여주는걸 이야기합니다) 이처럼 flow관련 logic이 들어가는건 UI의 역할범위 밖이라고 생각합니다.

현재 라이언하트는 MVC 아키텍처인데 리팩터링방향이 MVC에서 massive해지는 ViewController를 최대한 줄여보자입니다 물론 줄인다는게 물리적으로 코드자체를 줄이는것도있겠지만 그것보다 중요한건 ViewController의 역할을 나눠주는것이라고 생각했습니다. 그중에서도 화면전환로직을 그동안 viewcontroller가 담당하고 있었는데 이를 위해서 1️⃣다른 객체를 생성하고 2️⃣해당 객체로의 화면전환의 역할까지 viewcontroller가 맡는건 너무 많은 역할을 맡게한다고 생각했습니다

그래서 화면전환 로직을 담당해주는 coordinator pattern을 도입하게 되었습니다


Preview

  • 화면전환 로직을 담당하는 coordinator 객체가 존재합니다
  • delegate을 활용하여 user의 action을 대리자(coordinator 객체)에게 위임합니다

Coordinator 구조 및 설계

스크린샷 2023-10-14 오후 1 40 11
  1. AppCoordinator의 child로 SplashCoordinator가 존재합니다
  2. SplashCoordinator은 AuthCoordinator와 TabbarCoordinator를 child로 가집니다
  • loginviewcontroller에서 onboardingviewcontroller의 화면전환을 authcoordinator가 담당합니다
  • onboardingviewcontroller혹은 loginviewcontroller에서 tabbarcontroller로의 전환은 splashcoordinator가 담당합니다
  1. tabbarcoordinator는 각 tab에 있는 coordinator를 시작해주는 역할을 담당합니다(tabbar내부의 coordinator의 parent는 tabbar coordinator가 아닌 splashcoordinator로 설정해줍니다)
  2. tabbar내부에있는 coordinator들은 bookmark나 mypage로 이동할때 각각의 coordinator를 child로 가지게됩니다
  • tabbar내부의 모든 viewcontroller에서 mypage와 bookmakr에 접근할 수 있습니다

Coordinator Pattern 사용법

  1. 각각의 ViewController가 unique한 Navigation Interface를 가지고 있습니다
  • 예를 들어서 todayViewController의 경우 user가 오늘의 아티클을 tap하는 gesture가 존재합니다

이때 interface의 메서드의 경우 flow를 명확하게 알수있는 네이밍을 지양합니다(캡슐화에 위배)
단순히 유저가 어떤 action을 취했는지만을 나타내는 메서드명을 사용합니다
예를들어서 유저가 오늘의 아티클을 tap했을때의 결과가 명확한 todayArticleViewPresent같은 네이밍은 캡슐화를 위반합니다

protocol TodayNavigation: ExpireNavigation, BarNavigation {
    func todayArticleTapped(articleID: Int)
}
  1. 각각의 viewController라는 coordinator라는 interface타입의 변수를 약한참조로 들고있습니다

delegate패턴처럼 사용하기 때문에 강한순환참조가 발생할수있기때문에 이를 예방하기 위해 weak으로 선언합니다

final class TodayViewController: UIViewController {
    weak var coordinator: TodayNavigation?
    private let manager: TodayManager
    ...
}
  1. todayviewcontroller의 화면로직을 담당하는 todaycoordinator를 todayviewcontroller의 대리자로 설정해줍니다
final class TodayCoordinator: Coordinator {
    weak var parentCoordinator: Coordinator?
    
    var children: [Coordinator] = []
    
    var navigationController: UINavigationController
    
    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }
    
    func start() {
        showTodayViewController()
    }
    
    func showTodayViewController() {
        let todayViewController = TodayViewController(manager: TodayManagerImpl(articleService: ArticleServiceImpl(apiService: APIService())))
        todayViewController.coordinator = self
        self.navigationController.pushViewController(todayViewController, animated: true)
    }
}
  1. 실제 interface를 구현해주고 해당구현부에서 화면전환 로직을 구현해줍니다
extension TodayCoordinator: TodayNavigation {
    func todayArticleTapped(articleID: Int) {
        let articleCoordinator = ArticleCoordinator(
            navigationController: navigationController,
            articleId: articleID
        )
        articleCoordinator.parentCoordinator = self
        children.append(articleCoordinator)
        articleCoordinator.start()
    }
}

Navigation(Coordinator Interface)분리

  • tabbar내부에 있는 각 tab에서 모두 bookmark와 mypage로 접근이 가능한 각각의 custom navigation bar가 존재합니다 또한 아래와 같은 규칙을 가지고 있습니다
  1. 각 tab의 coordinator의 첫 viewcontroller의 navigation bar는 bookmark로 이동하거나 mypage로이동할 수 있는 기능만 존재한다
  2. 각 coordinator의 첫번째이후의 viewcontroller부터는 navigation bar가 pop혹은 dismiss의 기능을 수행할 수있다

따라서 아래와같은 세가지의 interface를 통해서 중복구현을 방지하였습니다

protocol BarNavigation: AnyObject {
    func navigationRightButtonTapped()
    func navigationLeftButtonTapped()
}

protocol PopNavigation {
    func backButtonTapped()
}

protocol DismissNavigation {
    func closeButtonTapped()
}

예를들어서 todayviewcontroller의 경우에 화면전환의 경우가 세가지가있는데

  1. navigationbar를 통한 bookmark로의 화면전환
  2. navigationbar를 통한 mypage로의 화면전환
  3. 오늘의 아티클 tap을 통한 오늘의 아티클상세뷰로의 화면전환

공통되는 navigationbar의 interface를 분리함으로서 아래와같은 navigation interface를 가질 수있게됩니다

protocol TodayNavigation: ExpireNavigation, BarNavigation {
    func todayArticleTapped(articleID: Int)
}

앱종료관련 navigation interface

  • 로그아웃을 하거나 회원탈퇴를 하면 앱을 강제로 종료시키는 기능이 존재합니다
  • 추가로 유저가 앱을 사용하는 중에 token이 만료되었는데 api를 호출하는 경우에도 잘못된접근이라는 앱재실행이 필요한 오류가존재합니다

해당 상황이 발생하는 경우를 추상화하기위해서 아래와같은 interface로 분리를 해줬습니다

protocol ExpireNavigation: AnyObject {
    func checkTokenIsExpired()
}

그리고 해당 interface의 동작이 모든 경우에 동일하기때문에 채택한 모든 coordinator에서 구현해줄 필요가 없으며 coordinator라는 프로토콜을 채택한 객체에서 해당 interface를 구현해줄 필요가 있기때문에 protocol의 extension을 활용해 기본구현을 해주고 where절을 통해 coordinator객체 내부에서만 기본구현이 존재하도록 구현했습니다

extension ExpireNavigation where Self: Coordinator {
    func checkTokenIsExpired() {
        UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            exit(0)
        }
    }
}

⚠️ bookmark와 mypage로의 화면전환을 protocol의 extension으로 구현하지 못한 이유

  • 구현체의 중복을 방지하기위해서 protocol의 extension을 활용해서 기본구현체를 만들었습니다
  • 하지만 bookmark와 mypage로의 화면전환 구현체가 몇개의 coordinator에서 동일하게 채택하고 있으며 실제 구현체의 모양도 완전히 동일함을 알 수있습니다
func navigationRightButtonTapped() {
    let mypageCoordinator = MypageCoordinator(navigationController: navigationController)
    mypageCoordinator.start()
    children.append(mypageCoordinator)
}

func navigationLeftButtonTapped() {
    let bookmarkCoordinator = BookmarkCoordinator(navigationController: navigationController)
    bookmarkCoordinator.start()
    children.append(bookmarkCoordinator)
}

하지만 이런 코드를 protocol의 extension으로 뺄수 없습니다
가장 큰 이유는 bookmark의 화면전환 로직을 bookmakrcoordinator가 담당하고 mypage의 화면전환로직을 mypagecoordinator가 담당하고있기때문에 해당 coordiantor를 상위 coordinator의 child에 append해줘야하는데 이를 extension을 통해서 기본구현을 제공하게 된다면 어떤 coodinator의 child에 append를 해줘야할지 알 수가 없습니다

다른 앱을 보면 각각의 tab의 navigationbar가 동일한 view로 이동할수있게 되어있는 앱이 거의 존재하지 않지만 라이온하트앱은 모든 tab의 viewcontroller에서 모두 동일한 뷰로 이동할 수 있는 navigationvbar 형태를 가지고 있기때문에 이러한 상황이 발생했습니다

해당 부분을 해결하기 위해서는 ux와ui적인 수정이 필요합니다


Coordinator pattern 도입 후기

  • 코디네이터에게 화면전환을 요청하고 코디네이터가 화면전환을 수행한다는 메커니즘을 코드로 구현하는게 익숙하지 않았음
  • 라이언하트의 경우엔 화면전환이 없는 편이 아니라고 생각해서 코디네이터 패턴을도입하기 적당한 규모의 프로젝트라고 생각했지만 이보다 작은규모의 프로젝트라면 도입을 깊게 고민하는편이 좋아보임(생각보다 러닝커브와 코드양과 파일이 많아짐)
  • 다양한 방식중에 child와 parent관계를 가지고 있는 코디네이터패턴 방식을 사용했는데 언제 child에서 코디네이터를 지워줄지를 구조설계단에서 고민을 하지 않으면 navigation stack이 계속 쌓여서 문제를 발생시킬수있음
  • DI까지 진행한 후라 viewcontroller객체 생성 + 데이터 전달 + 화면전달 코드가 결코 적지 않았는데 해당 코드들이 확실히 줄어서 깔끔해 보이긴했다. 또한 화면전환 분기처리도 viewcontroller의 user action에 따라 coordinator내부에서 분기처리를 해줘서 viewcontroller자체의 역할도 줄어들었지만 코드의 양도 줄어들었다

📮 관련 이슈

@kimscastle kimscastle added 🦁민재 민재's 🦁의성 의성's 🦁찬미 찬미's ✨Feat 새로운 기능 구현 ♻️Refactoring 리펙터링 labels Oct 14, 2023
@kimscastle kimscastle added this to the 🦁1차 Refactor🦁 milestone Oct 14, 2023
@kimscastle kimscastle changed the title [REFACTOR] 코디네이터패턴 적용 (#139) [REFACTOR] Coordinator Pattern 적용 (#139) Oct 14, 2023
@cchanmi
Copy link
Member

cchanmi commented Oct 14, 2023

수정 사항 없는 것 같아요
고생하셨습니다 👏

Copy link
Contributor

@ffalswo2 ffalswo2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6시간의 사투끝에..뿌듯하네요😁 주석 남은 것만 마저 지우고 머지합시다!

ViewController는 자신 이외의 다른 ViewController에 대한 어떠한 정보도 가져서는 안됩니다. 하지만 네이밍에서 이미 다른 ViewController에 대한 정보를 내포하고 있기에 이러한 이유로 캡슐화에 위반됩니다. (상세 이유 추가)

해당 문장 protocol 메서드 이름 캡슐화 위반 내용관련해서 이유를 상세하게 조금 더 추가했습니다 ! 한번 확인부탁드립니다.

@kimscastle
Copy link
Contributor Author

6시간의 사투끝에..뿌듯하네요😁 주석 남은 것만 마저 지우고 머지합시다!

ViewController는 자신 이외의 다른 ViewController에 대한 어떠한 정보도 가져서는 안됩니다. 하지만 네이밍에서 이미 다른 ViewController에 대한 정보를 내포하고 있기에 이러한 이유로 캡슐화에 위반됩니다. (상세 이유 추가)

해당 문장 protocol 메서드 이름 캡슐화 위반 내용관련해서 이유를 상세하게 조금 더 추가했습니다 ! 한번 확인부탁드립니다.

좋네요

@kimscastle kimscastle merged commit 8f2a40f into main Oct 14, 2023
@LentoAssai
Copy link

👏👏👏👏👏

분명한 의도와 그에 맞게 구현하신게 인상깊네요

몇 가지 궁금한 점과 얘기해보고 싶은게 있어서 커멘트 남깁니당


1. Coordinator들의 계층 관계

앱을 안돌려봐서 직관적인 이해가 어려운데, Coordinator들의 계층 관계 기준은 어떻게 잡으셨나요??


2. Coordinator 프로토콜의 start()

많은 레퍼런스에서 Coordiantorstart() 메소드를 넣어서 알려주고 있고,

이때 interface의 메서드의 경우 flow를 명확하게 알수있는 네이밍을 지양합니다(캡슐화에 위배)
단순히 유저가 어떤 action을 취했는지만을 나타내는 메서드명을 사용합니다
예를들어서 유저가 오늘의 아티클을 tap했을때의 결과가 명확한 todayArticleViewPresent같은 네이밍은 캡슐화를 위반합니다

이러한 사항도 고려해서 아래와 같이 start에소드에는 어떤 일을 수행하는지 알 수 있는 네이밍을 가진 메소드를 호출하는 방식으로 구현하신 것 같습니다.

func start() {
    showTodayViewController()
}
    
func showTodayViewController() {
    let todayViewController = TodayViewController(manager: TodayManagerImpl(articleService: ArticleServiceImpl(apiService: APIService())))
    todayViewController.coordinator = self
    self.navigationController.pushViewController(todayViewController, animated: true)
}

해당하는 코드들을 보면서 그냥 start 메소드를 프로토콜에 정의해 놓지 않는 방식으로 구현하면 보일러 플레이트를 줄이면서 목적에서는 벗어나지 않지 않을까 생각이 들었습니다.


3. CoordinatorViewController 내포관계

앞의 네이밍과 관련하여, Coordinator의 네이밍이 위 PR과 같다면
ViewControllerCoordinator를 가지는 상황이고, ViewController에서의 가독성은 좋지만,

만약 나중에 더 디밸롭되어서 Coordinator 자체가 어떤 플로우를 실행할 지 결정하고, 어떤 ViewController를 보여줄 지 결정하는 식으로 구현이 된다면, Coordinator의 메소드 네이밍이 다소 어색할 수 있다고 생각이 듭니다.

또는, 위 상황까지 안 가더라도 제 개인적인 생각으로는 ViewController에서 어떤 액션으로 Coordinator의 어떤 메소드를 호출할 때, 액션은 알 수 있으니, Coordinator의 메소드 명이 굳이 해당 액션을 나타내지 않고 Coordinator 내에서 어떤 동작을 하는지를 나타내면 좋겠다고 생각이 듭니다.

아니면 NavigationViewController를 위한 프로토콜로 두고, Coordinator를 위한 프로토콜을 하나 더 정의하여 어댑터를 이용해 둘을 연결해도 좋을 것 같습니다.


4. Self-deallocating coordinator

이전에 프로젝트에서 위와 같이 parent, children을 통해 메모리를 관리하는 방식 대신, 참조 관계를 조금 바꾸어 스스로 메모리 할당과 해제를 할 수 있게끔 Coordinator패턴 적용해보았는데, 개인적으로는 이 방법이 더 나은 방법이라고 생각됩니다.

이 패턴으로 구현할 때의 불편했던 점이나 이슈가 있었나요?

@kimscastle
Copy link
Contributor Author

kimscastle commented Oct 15, 2023

오랜만이네요 승찬쿤😀

@LentoAssai
코디네이터 패턴을 적용하는 경험을 통해서 답변할수있는 부분들이좀 있어서 최대한 자세히 답변을 적어봤습니다

1. Coordinator들의 계층 관계

기본적으로 coordinator의 계층관계기준은 기획단에서 나온 와이어프레임으로 잡았습니다, 앱자체의 flow는 특정기능을 기획단에서 어떻게 정의하느냐에 따라서 달라질수있다고 생각했던것도 있겠네요

스크린샷 2023-10-15 오후 9 22 33

이게 저희의 와이어프레임인데 노란색부분이 핵심기능이자 다른기능과 분리되는 기능으로 명세를 해놨습니다
마이페이지같은경우는 추후에 탭이 아닌 navigation flow로 변경이 되었구요

그리고 두번째는 의미상의 rootviewcontroller의 설정여부입니다. 어떤 viewcontroller가 특정 flow의 최상단 viewcontroller로 인식할것인가에 대한 기준입니다, splashcoordinator와 authcoordinator를 분리한 이유가 이런 이유라고 이해하시면될것같습니다. authcoordinator의 최상단 vc는 login이고 splashcoordinator의 최상단 vc는 splashviewcontroller입니다. 초기 설계에서는 splash와 login모두 authcoordinator에 포함되어있었는데요 login의 경우에 navigationcontroller를 통해서 splash로 push나 pop을 하지 않으니 각각의 vc가 특정 coordinator로 분리되어있는것이 자연스럽다고 생각했습니다, onboarding에서 login으로는 이동이 가능하니까 같은 coordinator에 있는게 맞다고 생각했습니다

우선적으로는 기획단에서의 기능단위로 확실히 나눌수있는 경우엔 와이어프레임기준으로, 그게 애매하다면 의미상의 rootviewcontroller를 통해서 coordinator의 계층관계를 잡았습니다

앱자체는 생각보다 flow를 확실히 나눌 수 있는 형태입니다

애초에 coordinator가 하는 역할이 화면전환로직을 담당한다면 특정 coordinator는 특정한플로우의 화면전환로직을 담당하는게 맞다고 생각했습니다

여기까지는 다른 앱과 flow가 크게 다르지 않지만 각 tabbar왼쪽에 있는 부분이 coordinator의 흐름을 조금은 덜 직관적으로 만들게된 원인이되었다고 생각합니다 모든 탭에서 bookmark관련 flow와 mypage관련 flow를 시작할 수있어서 이렇게 coordinator를 나누게 되었습니다, 이 경우에는 와이어프레임이 기준이었다고 할수있겠네요

2. Coordinator 프로토콜의 start()

많은 레퍼런스를 참고했던 부분이라서 크게 신경쓰지 못했던 부분이었는데 coordinator는 기본적으로 flow를 담당하는 객체이고 그렇기때문에 메서드자체에서 flow에 관한 네이밍이있는건 어색하지 않다고 생각하네요

저 위에서 적은 캡슐화에 대한 내용은 viewcontroller의 역할에 맞지않는 메서드명으로 인해서 캡슐화를 위반한다의 의미였습니다 단순히 ui의 역할을 해야하는 viewcontroller가 flow로직에 대한 관심사분리가 안되어있다는 뜻이었는데 coordinator의 경우엔 flow를 담당하기때문에 오히려 start라는 메서드 자체가 오히려 역할을 명확하게 보여주지 못한다는 느낌이 드네요 해당 부분은 고민을 해봐야겠습니다

사실 start를 했을때 어떤 flow가 발생하는지를 모르면 어떤 side effect를 발생시킬지 모르는거니까요

3. Coordinator와 ViewController 내포관계

네이밍 관련해서 coordinator자체를 delegate형태로 사용하기에 어떤 action을 전달해주고 위임자인 coordinator에서 실제 구현부를 구현하는 방식이기에 어색하다는 생각은 해보지 못했던것같습니다 그래서 네이밍이 action과 관련되도록 만들어둔것이기도 합니다

그래서 예를들어서 authcoordinator의 경우에 coordinator자체가 취할수있는 action이 loginviewcontroller를 보여주거나 onboardingviewcontroller를 보여준다고 했을때 coordinator자체의 액션을 직관적으로 보여줄 수 있는 네이밍을가진 메서드를 가지고

func showLoginViewController() {...}
func showOnboardingViewController() {...}

실제로 viewcontroller의 delegate메서드에서 coordinator의 위와같은 메서드를 직접호출하는 방식으로 변경한다면 실제 coordinator가 자체의 수행 메서드를 가지고 있고delegate의 수행을 위임받을때 메서드자체를 호출시키는 방식은어떨까요?

func startButtonTapped() {
	self.showLoginViewController()
}

delegate자체의 네이밍은 구체적인 action자체를 몰라야한다라는 부분은 맞다라는생각이들어서 coordinator의 메서드와 delegate의 메서드를 구분지어서 사용한다면 어색하지 않을거같다는 생각이 듭니다

4. Self-deallocating coordinator

처음 coordinator패턴을 적용해야겠다는 결정을 내리고 여러 레퍼런스를 참고하면서 child를 가진 구조를 공부하고 적용하는데 꽤나 어려운점이 많았던건 사실입니다. 우선 첫번째로 child에서 할당해제를 시켜줘야하는 부분을 파악하고 있어야 coordinator의 어떤 메서드를 호출했을때 실제로 child에서 해제시킬수있기때문에, 전체적인 구조설계과정을 잘 파악하고 있지 않으면 모르는사이에 메모리 릭이발생하거나 네비게이션 stack이 과도하게 쌓일수있다는 단점은 존재합니다

하지만 반대로 말해서 설계자체의 이해도를 가지고있다면 오히려 coordinator flow의 사용시점과 종료시점을 명확하게 구분지을수 있다는 장점또한 가지고 있다고 생각합니다. 그리고 이부분이 오히려 coordinator의 설계를 할때 명확하게 구분지을수있는 근거가 되기도 했습니다

child방식으로 진행하면서 겪은 어려움이 한가지 있었는데요 설계도를 보시면 tabbarcoordinator가 todaycoordinator, curriculumcoordinator, articleCatetgorycoordinator, challengecoordinator를 가지고있고 해당 coordinator를 상위인 splashcoorinator의 child로 설정해줍니다. 하지만 앱의 기획단계에서 todaycoordinator, curriculumcoordinator, articleCatetgorycoordinator, challengecoordinator에서 마이페이지에 접근을 할때 mypagecoordinator를 child로 갖게되는데 문제는 mypage에서 회원탈퇴를 하게되면 splashviewcontroller에 진입해야해서 splashcoordinator에 접근을 해야겠습니다. 하지만 parentcoordinator에서 다운캐스팅을 통해서 상위 coordinator에접근하고 거기서 상위 코디네이터인 splashcoordinator에 접근을 해야하는데 mypage의 경우에 상위 coordinator가 todaycoordinator, curriculumcoordinator, articleCatetgorycoordinator, challengecoordinator중에 어떤건지를 알수가 없게됩니다(물론 다운캐스팅을 guard문으로 일일이 하면되겠지만 어색하다고 느꼈습니다) 당연히 상위 coordinator를 특정할수없으니 그 상위코디네이터도 특정할수없게되는거죠

그래서 다른 앱들을 보니 회원탈퇴로 접근할수있는 flow가 특정되는 앱들이 대다수인걸 알 수 있었습니다. 만약에 우리앱도 mypagecoordinator의 parent가 특정코디네이터로 결정된다면 splashcoordinator에 접근이가능했을겁니다

추가적으로 라인앱의 경우엔 회원탈퇴시 앱이 종료된다는 alert가 뜨고 앱이 종료되는 레퍼런스를 찾았고 해당방식이 ux적으로 어색하지않아서 앱을 종료시키는 flow로 변경을하기도 했습니다

이런부분이 조금 불편하거나 이슈이긴했지만이건 coordinator의 child문제라기보다는 앱자체의 설계문제였다고 생각합니다

만약에 child없이 모든 coordinator가 같은 위계에 있다면 어떤 coordinator에서도 다른 coordinator로 접근이 가능하기에 이런 이슈가 발생하지 않았을거같긴하네요

@LentoAssai
Copy link

LentoAssai commented Oct 16, 2023

많은 고민을 한 것이 여실히 드러나네요 멋집니다👍

1. Coordinator 계층관계

여기까지는 다른 앱과 flow가 크게 다르지 않지만 각 tabbar왼쪽에 있는 부분이 coordinator의 흐름을 조금은 덜 직관적으로 만들게된 원인이되었다고 생각합니다 모든 탭에서 bookmark관련 flow와 mypage관련 flow를 시작할 수있어서 이렇게 coordinator를 나누게 되었습니다, 이 경우에는 와이어프레임이 기준이었다고 할수있겠네요

이 부분이 잘 이해가 되지 않긴 하지만, 제가 이해한걸로 생각해보면 그림에서의 아티클 상세 페이지BookmarkCoordinator, MyPageCoordinator등과 같이 여러 곳에서 사용될 수 있는 친구들을 따로 분리하여 child로 attatch하는 방식으로 구현할 수 있겠네요

개발할 때 다른 부분도 그렇지만, 플로우를 정하고 Coordinator를 정하는 일은 충분한 설계가 밑받침 되어야 하는데, 기능 별로 확실하게 분리할 수 있게끔 설계를 해놓으신게 정말 좋은 것 같습니다

2. Coordinator 프로토콜의 start()

제가 요거 말씀 드린 이유는 아래 2가지 때문입니다.

  1. 보일러 플레이트 (start메소드와 그 밑에 실제 동작하는 메소드를 분리하여 2번 구현)
  2. ViewController입장에서의 관심사 분리 된 네이밍에 어긋남 (네이밍까지도 관심사를 분리하여 구현하는 개발 환경에서, start는 어색하게 느껴짐)

동작하는데는 당연히 아무 문제가 없지만, 개인적인 생각으로는 충분히 수정할 수 있을 것 같아서 말씀드렸어용

3. CoordinatorViewController 내포관계

😵‍💫 설명 적어 주신 게 잘 이해가 안되네요 ㅠㅠ

제가 하고 싶었던 말은 ViewController에서 이미 Coordinator의 메소드를 호출함으로써 해당 메소드 명과는 별개로 이미 관심사 분리는 되어있다고 생각이 들었습니다. (어디까지나 제 생각입니다)

class ViewController: UIViewController {
    private let coordinator: Coordinator

    ...

    func startButtonPressed() {
        self.coordinator.startButtonPressed()  // 1
        self.coordinator.presentStartView()     // 2
        self.coordinator.동해물과백두산이()         // 3 이러한 메소드 명을 가져도 관심사 분리는 되어있다고 생각합니다.
    }
}

그런데, 메소드 명 까지 관심사 분리의 영역에 포함하여 UIViewController에서 보이는 메소드 명을 실제 수행하는 내용이 아닌 액션을 나타낼 수 있도록 설정하였다면, Coordinator에서 또한 마찬가지로 구현할 수 있지 않을까란 생각이었습니다. (물론 이 또한 동작하는 데 아무런 문제는 없습니다

class MyCoordinator: Coordinator {
    func startButtonPressed() {    // 1 현재 - ViewController에서의 액션을 아는 메소드 명
        // present start view
    }

    func presentStartView() {         // 2 Coordinator 내에서 관심사 분리 된 메소드 명
        // present start view
    }
}

추가로 만약 ViewControllerCoordinator 둘 다 메소드 명 까지 관심사를 분리하여 모든 토끼를 잡고 싶다면 어댑터를 이용할 수 있을 것 같다고 말씀드린 거 였습니다.

4.Self-deallocating coordinator

parent - child로 관리를 할 때의 장점과 단점을 정확히 이해하고 사용하신 게 너무 좋네요
위에 적으신 것 처럼 분명히 Coordiantor의 수명을 직접 설정할 수 있는 것은 큰 장점이지만, 제가 Self-deallocating coordinator가 더 낫다고 생각하는 이유는, 아직까지의 제 경험으로는 Coordinator의 수명이 화면들에 의존적이기 때문에 그렇다고 생각하고 말씀드린 거였습니다.
막연히 이게 더 좋다! 라고 결론내리고 도입하는 것이 아닌, 장단을 살펴보고 어느 방식이 더 좋을지 고려하여 사용하는 과정 굿입니다 👏

진행하며 겪으신 어려움은 이미 다른 방식으로 해결이 된 것 같지만, 우선 설계를 할 때 보다 더 완벽하면 당연히 수월하게 작업할 수 있겠고, 해당 작업에서 MyPageCoordinator를 가질 수 있는 인터페이스를 하나 더 정의하여 구현할 수 있지 않을까 라는 생각입니다.

@kimscastle kimscastle deleted the refactor/#139-coordinator branch October 17, 2023 01:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✨Feat 새로운 기능 구현 ♻️Refactoring 리펙터링 🦁민재 민재's 🦁의성 의성's 🦁찬미 찬미's
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[REFACTOR] Coordinator Pattern도입
4 participants