Skip to content

Releases: SHcommit/SHCoordinator

[Fix] FlowCoordiantorNavigatable handlePopViewController로직 개선

09 Apr 07:06

Choose a tag to compare

FlowCoordiantorNavigatable.handlePopViewController(_:didShow:animated:)의 개선점은 다음과 같습니다.

A Coordiantor에서 presenter = delegate 후 A VC가 dismiss될 때 이 로직으로 coordiantor.finish()가 호출됬다면,
A Cooridnator의 preesnter에서 다른 VC로 전환 후 다시 A VC로돌아올때도 내부에 coordiantor.finish가 호출됩니다.
이 문제점을 수정했습니다.

FlowCoordinator 에서 init제거

29 Oct 07:51

Choose a tag to compare

FlowCoordinator 에서 init제거.

상황에따라 코디네이터 생성시점에 추가적인 값들을 기반으로 뷰를 생성해야함. 또는 init(presenter: UINavigationController)가 사용되지 않는 곳도 있음 ex,, ApplicationCoordinator, mainCoordinator

FlowCoordinator protocol에 불필요한 선언 제거

29 Oct 07:27

Choose a tag to compare

FlowCoordinator protocol에 불필요한 viewController 선언 제거

README에 업데이트된 release 버전 추가

27 Oct 16:31

Choose a tag to compare

요약

리드미에 3.0.x으로 업데이트된 릴리즈 버전 추가했습니다.

3.0.0

27 Oct 16:17

Choose a tag to compare

📌 [요약]

  • protocol을 좀 더 세분화 했습니다.
  • finish()와 popViewController를 번거롭게 매번 호출해야하는 로직을 내부에 처리했습니다.
    • finish함수 호출시에 presenter?.popViewController(animated:) 호출을 할 수 있도록 했습니다.
  • 뷰 컨트롤러에서 명시적으로 popViewController를 호출하지 않는 경우를 대비해 FlowCoordinatorNavigatable 프로토콜을 추가했습니다.
  • 상세 finish(), popViewController를 뷰컨트롤러마다 선언해야하는 번거로움 해결했습니다.
  • 강제 언래핑 옵셔널 연산자 대신 옵셔널 연산자로 대체.

📸 [작업 내용]

고민1: 만약 코디네이터에서 뷰 컨트롤러 인스턴스를 참조할 경우 무조건 뷰컨트롤러 코드 내부에 popViewController를 호출하는 scope에 popViewController, finish()를 호출해야하는 번거로움....

image

또는

image

코디네이터에서 매번 finish()외 네비게이션의 pop 함수를 호출 코드를 wrapping하는 함수를 선언해야 하는경우...

image

또는 뷰컨트롤러 deinit시점 finish()호출해야하는 경우

코디네이터에서 start()내부에 생성한 뷰 컨트롤러를 코디네이터 내부에 참조할 경우, navigationController's popViewController가 호출되도, 해당 뷰 컨트롤러는 메모리로부터 해제되지 않습니다. (그 이유는 아래 나옵니다.) 그래서 반드시( 물론 네비스택, 코디네이터 이외에도 strong reference중인 객체가 있는경우 제외) popViewController호출시점에 coordinator.finish()를 호출해서 strong reference로 retatin count가 2인 뷰컨트롤러를 해제해야 했었습니다.

이때 생각한 문제는... 만약 baseViewController를 통해 커스텀 네비바를 생성하고, 뒤로가기버튼을 정의했다면? 이때 뒤로가기 액션헨들러 @objc 함수가 private이라면? override가 불가능합니다.
또한 네비게이션 바 백 바 버튼을 커스텀으로 부착하지 않는 경우. 즉 시스템에서 자동으로 백 바 버튼을 생성해주는 경우입니다. 이땐 더더욱 곤란해집니다. 물론 코디네이터 객체 안에서 weak var viewController로 뷰컨트롤러를 참조하고 있다면, 코디네이터 객체에서 뷰컨트롤러를 선언하지 않는 느낌과 동일하게 유일하게 strong reference중인 내비게이션 스택에서 pop되면 (외부 strong 참조중인 객체가 없을 경우) deinit가 호출되지만....

image

결론적으로 상황에 따라 다르지만, 기존 FlowCoordinator에서 선언했던 finish()를 제거하고 두 가지 경우를 고려한 delegate 프로토콜을 선언했습니다.

image

그리고 FlowCoordinator에서 두 가지 경우를 준수한 FlowCoordinatorDelegate 함수들을 extension으로 정의했습니다.

finish()의 경우 코디네이터에서 뷰 컨트롤러를 참조하지 않는 경우입니다. 이 경우 finish(withAnimated:)를 썼다간,, 이전 이전 화면으로 보여질 수 있습니다.
현재 뷰 컨트롤러가 사라질때 이 뷰 컨트롤러의 strong reference가 하나일 경우에 finish()호출.

만약 코디네이터에서도 strong reference중 총 2개인 경우 finish(withAnimated:)호출해야합니다. 물론 이 이상인 경우 뷰 strong reference중인 클로저나 객체의 nil이나 release를 시켜야합니다.


고민2: UIWindow를 다루는 코디네이터, viewController를 다루지 않는 코디네이터에서, 코디네이터들을 다루는 코디네이터 등 무조건 사용되지 않는 코디네이터에서도 필수적으로 viewController 인스턴스를 선언해야 하는가????

간단 요약 결론입니다: 매번 뷰 컨트롤러의 deinit시점이나 popVIewController 호출 sopce에서 coordinator.deinit(), navigationController.popViewController를 하지 않아도 된다.

if-tg팀 ios 석현이형과 release1.0.*�의 FlowCoordinator 프로토콜일 때 개발 중 같이 고민했던 경우입니다.

protocol에서 viewController 프로퍼티 { get }를 선언한다면 위 고민처럼 불필요한 프로퍼티의 선언이 발생됩니다.....

release 2.0.0에서 viewController 프로퍼티를 FlowCoordinator에서 제거했던 주요 이유중 하나입니다. �

잠시 잊고있었던... viewController의 deinit가 호출되지 않는다는점은 코디네이터를 배운지 오래되서 기억이 가물가물했지만, 최근에 다시 코디네이터를 다루며 해결 방법을 다시 완벽하게 떠올렸고 하단에서 설명됩니다.

결국 제거했지만 제일 주요했던 finish()와 popViewController를 매번 호출해야하는 번거로움을 해결하는 이번 리빌딩 과정에서 다시 추가했습니다. 그러나 또 제거했습니다. ㅎㅎ........ 결국 protocol내부에 사용되지 않는.. 불필요한 프로퍼티 선언이나 함수는 분해하는게 좋을 수도 있다고 배웠던 경험을 이 떠올랐고 프로토콜을 또 세분화 했습니다 +_+

이름하여!!!!!!!!

image

FlowCoordinatorNavigatable protocol!!

코디네이터가 finish()되야하는 경우는 코디네이터를 start()했던 viewController가 화면에서 더이상 보여지지 않을때도 메인 요소 중 하나입니다. 그러기에 앞에서 언급했듯,, 매번 finish(), popVIewController를 viewController 객체에서 호출해야했습니다.....

개발이 바뻐서 계속 미루다 오늘 시간내서 coordiantor 객체 내부에서 처리할 수 있도록 로직을 개편했고 고민2를 해결해줄 수 있는 key라 생각되서 FlowCoordinatorNavigatable을 추가했습니다.

결론적으로 가장 중요한 것은 공통적으로 발생되는 코드를 protocol Extension으로 처리해기 위한 고민을 했고, 이와중에 네비게이션 컨트롤러로부터 pop될때 pop중인 뷰 컨트롤러가! transitionCoordinator?.viewController(forKey: .form)이 해당 코디네이터에서 다루었던 뷰컨트롤러인지 메타타입을 비교하거나, strong reference중인 주소값을 비교해야만 했습니다.

그 로직이 FlowCoordinator extension에 존재했어야했지만, 이를위해선 뷰 컨트롤러 인스턴스로 참조할 수 있습니다. 그러나 고민1이 겹쳤었을 때 코디네이터에서 다루는 주요 뷰 컨트롤러를 컴플리션으로 구현하는게 어떨지 고민했었고...

image

클로저 추가 후

image

이렇게 실제 코디네이터 생성시점에 코디네이터에서 다루는 주요 뷰컨트롤러 구체타입을 비교해주는 로직을 작성도 해봤습니다.

image

(비교)

또는 type(of:)를 써서 비교도 해봤지만 결국 최고로 쉬운 경우는 프로토콜에서 viewController를 프로퍼티로 선언해 코디네이터가 관리하는 뷰컨트롤러의 주소값을 비교해 명확하게 뷰컨트롤러가 pop될때 해당 뷰가 코디네이터의 뷰인지 판별하기가 쉬웠습니다... 그러나 고민1로 인해 결국 새롭게 프로토콜을 세분화했습니다.

그리고 이 뷰 컨트롤러로 인해 얻을 수 다룰 수 있는 주요 기능 중 하나인 UINavigationControllerDelegate 프로토콜에서 pop될때 관련 네비게이션 함수 호출시점에 finish()를 호출해주는 코드 또한 viewController 프로퍼티 기반으로 구현해 줄 수 있었습니다.

설명이 길어져 관련 사용 예시는

SHCoordinatorExample -> Source -> Feature -> SubCoordinator에 코드로 작성했습니다.

✨[발생했던 이슈 이전 release 2.0.0 당시 문제: FlowCoordinator protocol에 � viewController: UIViewController를 둬야 하는가?]

아래는 release 2.0.0 일 때 코드입니다.

  protocol FlowCoordinator {
  ...
  var viewController: UIViewController { get }
  ...
}

ACoordinator: FlowCoordinator {
  ...
  let viewController: UIViewController
  ....
}

viewController를 FlowCoordinator 프로퍼티로 선언하지 않아도 FlowCoordinator를 준수하는 객체에서는 persenter(UINavigationController)가 있습니다., presenter.viewControllers.last를 통해 코디네이터가 다루는 뷰 컨트롤러를 얻어올 수 있었습니다. 그래서 꼭 코디네이터 구현체에서 viewController를 추가해도 되지 않지만, protocol 준수를 하게된 이상, @objc로 옵셔널타입으로 선언하지 않는 이상 명시적으로 viewController를 구체타입에 선언해야했습니다.

코디네이터 객체에 뷰 컨트롤러를 선언했을 때, 장 단점이 존재했습니다.

  • 장점은 viewController 인스턴스가 코디네이터에서 많이 활용된다면 정말 쉽게 인스턴스를 사용할 수 있습니다.
  • 단점은 뷰컨 선언을 'weak ' 키워드로 하지 않는다면? strong reference가 됩니다.

그러나 이 시점 코디네이터 객체는 해당 뷰 컨트롤러를 completion handler, combine등 클로저를 통해 참조하고 있지 않는다면 네비게이션 스택에서 pop될 때, 해당 코디네이터 객체.finish()를 호출함으로 일반적으로 뷰 컨트롤러를 deinit할 수 있습니다. (물론 이 가정은 뷰컨트롤러를 네비게이션 스택, 코디네이터 객체 속 프로퍼티 총 2개의 strong reference가 발생될 경우를 의미합니다. 그 외에 위에서 언급한 캡쳐 형식으로 참조하는 경우는 viewController가 메모리로부터 release 되지 않을수 있고, memory leak가 발생하게 됩니다.)

ACoordinaotr.start() // AViewController 생성 후 실행. 이때 AViewController가 pop될 때 더 이상 ACoordiantor 인스턴스도 유지될 이유가 없다면, 메모리로부터 해제해야 하지만, 해제하기 위한 함수 finish()를 반드시 호출해야하고, 일반적으로는 AViewController 내에서 popViewController(animated:)를 호출시키는 scope내에서 자신을 다루는 코디네이터의 finish() 또한 호출해야 했었습니다. (잘못하면 깜빡함..)

이때도 두 가지 경우가 있습니다.

첫 경우

release 2.0.0기준으로 코디네이터 객체에서 start()내부에 생성한 뷰 컨트롤러의 인스턴스를 프로퍼티로 strong reference하지 않는 경우. 즉 네비게이션 스택에서 유일하게 해당 뷰 컨트롤러를 strong reference할 때

image

popViewController가 호출될 때, 이 scope에서 coordinator.finish()호출해도 되고,

아니면 뷰 컨트롤러를 strong reference로 참조하는 뷰 컨트롤러가 네비게이션 스택 하나뿐이므로

image

deinit시점에 coordinaotr.finish()를 선언해도 됩니다.

두번째 경우

코디네이터 객체에서 코디네이터객체.start() 내부에 생성한 뷰 컨트롤러 인스턴스를 해당 코디네이터에서 인스턴스로 보유중일 때

이땐 popViewController가 호출되는 시점에서, 이 scope에서 coordinaotr.finish()를 호출해야합니다.

물론 이마저도 뷰 컨트롤러를 다른 객체가 strong reference중일 경우엔, 뷰 컨트롤러는 여전히 메모리에 로드되어있어 leak가 발생됩니다. ( 관련 예제 포스트 링크 )

위에서부터 계속 언급해왔던 문제를 최소화 하기위해 결국 네비게이션 델리게이트를 다시 활용하게 되었습니다.

[2.0.0] update FlowCoordinator

18 Sep 06:37

Choose a tag to compare

  • delete invalid properties
  • append very very very simple example

Version 1.0.2: Updated README with Detailed Changes

29 Jun 18:15

Choose a tag to compare

First Release SHCoordinator LIbrary ver 1.0.1

29 Jun 06:46

Choose a tag to compare

First Release SHCoordinator LIbrary

29 Jun 06:01

Choose a tag to compare

A library that can be easily used when introducing the Coordinator Pattern