[realm] 아키텍쳐와 디자인 패턴

Valse·2022년 8월 8일
1

Swift

목록 보기
5/8
post-thumbnail

⚠️ 본 게시글은 realm의 영상 게시글과 해당 강의자의 블로그를 보고 정리한 글 입니다.
따라서 글쓴이의 주관이 매우 많이 포함되어 있음을 밝힙니다.


1. 디자인 패턴?

디자인 패턴은 은탄환이 아니며, 용도에 맞게 사용하지 않으면 아키텍쳐는 엉망이 된다.
따라서 그 '용도'에 대한 이해가 잡혀있지 않다면 실용성, 효율성, 협업탄력성 등이 저하된다.

1.1. 싱글톤

싱글톤은 태생적으로 좋은 의도를 갖고 있으나, 대체로 남용되는 경향이 있다.
데이터베이스에 접근하거나 하나의 인터페이스만 허락되는 상황이라면 싱글톤은 권장할 만하다.
그러나 사람들은 싱글톤을 전역/shared 상태로 둔 속성을 동기화하는 방식으로 남용한다.

  • 싱글톤이 노출되지 않을 수록 아키텍쳐의 구조는 깔끔해진다.

  • 만약 로그 메시지를 위해 싱글톤이 남용되고 있다면, 내부 구조가 싱글톤 패턴으로 구성된 Loggable 포로토콜로 해결할 수 있다.

  • networkManager 의 경우에도, 의존성 주입으로 싱글톤을 대체할 수 있다.

1.2. 싱글톤의 대안, Composition 패턴

Composition 패턴은 하나의 책임을 갖고 반복되지 않는 코드 작성에 매우 유리하게 돕는다.
이렇게 작성된 객체는 특수하지만 재사용성이 뛰어나다.
이 패턴은 복합 객체와 단일 객체를 동등하게 취급하는 것을 목적으로 트리 구조로 작성한다.
따라서 전체(Composite)와 부분(Leaf) 관계를 트리 구조로 표현할 때 주로 사용한다.
전체는 하나(이상) 의 명세(Component)를 따르고, 이 명세는 전체와 부분이 동일하게 공유하기 때문에
두 객체를 동일하게 취급할 수 있다.

이 부분 객체들을 다시 새로운 인스턴스를 만들 수 있는데, 이때 의존성 주입 패턴이 유리하게 작동한다.
굳이 새로운 부분 객체를 하드 코딩으로 생성할 필요 없이, 부분 객체를 찍어내는 방식으로 코드를 작성할 수 있다!

2. MVC 아키텍쳐

MVC는 대체로 여기저기서 조금씩 다른 초점으로 비추어지고 있습니다.
본 글의 MVC는 위 강의의 저자 의견을 준용합니다.

고전적인 MVC 아키텍쳐는 웹에 적합하다.
컨트롤러는 모델의 데이터를 가져오고, 이를 뷰에 표시한다.
모델의 변경에 따라 컨트롤러는 새로 뷰를 만든다.

Apple의 MVC는 뷰와 모델을 중재하는 ViewController의 비중이 작아지길 바랐으나, 반대로 매우 부피가 커지곤 한다.
또한 뷰의 라이프 사이클과 매우 밀접하기 때문에 내부 분리가 쉽지 않다.
뷰컨은 재사용되지 않고, 이 뷰컨에 딸려있는 뷰 또한 재사용이 거의 불가능하기 때문에 모델만 재사용된다.
-> 이는 리소스의 낭비다.

MVC에는 Composition 패턴도 없고, 의존성 주입도 없다.
그래서 아키텍쳐라기보다는.. UI 패턴에 더 가깝다.


3. VIPER

최초로 전체 아키텍쳐를 다룬 훌륭한 패턴으로써, 몇 가지 새로운 개념과 등장했다.
비즈니스 로직을 담당하는 Interactor가 있다. 이는 Composition을 사용하는 다른 서비스와 연동된다.
Router는 뷰와 뷰컨 사이에 다른 프레젠테이션 맥락 적용이 가능하며, 같은 화면을 여러 곳으로 보낼 수 있다. 쉽게 비유하자면 segue 역할을 한다. 어떤 뷰로 언제 어떻게 갈지 로직을 갖고 있다.
Presenter 는 UIKit 대신 화면에 나타낼 데이터에 대한 포맷 구성 작업을 담당한다.

패턴 자체는 훌륭하나, 기반 코드를 많이 작성해야 한다. 그래서 대체로 코드 생성기를 쓴다.


4. MVVM

MVVM은 iOS 에서 가장 많은 인기를 누리고 있다.
Model - View - ViewModel 의 줄임표현이다.

ViewModel은 테스트가 가능하기에 꽤나 쓸만하고, 이 모델 내부엔 UIKit 관련 코드가 없다.
즉, 뷰 모델은 ViewController 로딩과 의존성이 없기에 ViewModel에서 비즈니스 로직 테스트가 가능하다.

UIViewControllerUIView는 함께 View로 분류된다.

MVVM을 꼭 FRP(Function Reactive Programming) 구조로 작성할 필요는 없다.
즉, MVVM 아키텍쳐에게 Rx, ReactiveCocoa가 필수 라이브러리는 아니다.
그러나 이것의 부재가 MVVM의 단점인 많은 기반 코드 작성으로 귀결된다.
대체로Rx가 바인딩을 돕기 위한 라이브러리로 사용된다.

MVVM의 가장 큰 장점은 테스트의 용이성에 있다.
다만 VIPER의 Router 역할 없이, 어떻게 뷰 컨트롤러와 바인딩할 수 있을지가 문제로 남는다.
Apple은 이를 위해 의존성 주입을 권장했으나, 이는 꽤나 문제적이다.


4.1. MVVM 의존성 주입의 문제

만약 뷰컨 A에 뷰컨 B의 의존성을 주입하고 다시 뷰컨 C의 의존성을 주입하면, 뷰컨 A는 모든 뷰컨의 의존성을 갖게 된다.
앱 하나가 갖는 NavigationController 의 수만큼이나 이런 현상은 많이 발생할 수 있다.
가독성이 매우 떨어지고 실제로 필요한 것과 필요 없는 것이 무엇인지 구분하기에 어렵다.

버튼 하나만 눌렀는데, 그 내부에 Child 뷰컨이 또 다른 객체 의존성을 갖고 생성되고
if문으로 디바이스 상태에 따라 또 서로 다른 뷰컨과 보여주기 방식이 작성되고 있다.
이처럼 Router의 부재는 MVVM의 테스트 용이성에 걸림돌이 된다.

func doneButtonTapped() {
    let vc = ChildViewController(prepareNeccessaryState())

    if Device.isIPad() {
        navigationController.pushViewController(vc, animated: true, completion: nil)
    } else {
        var nav = UINavigationController(rootViewController: vc)
        nav.modalPresentationStyle = UIModalPresentationStyle.Popover
        var popover = nav.popoverPresentationController
        popoverContent.preferredContentSize = CGSizeMake(500,600)
        popover.delegate = self
        popover.sourceView = self.view
        popover.sourceRect = CGRectMake(100, 100, 0, 0)

        presentViewController(nav, animated: true, completion: nil)
    }
}

4.2. MVVM과 Router의 부재

Router 하나의 부재로 인해 적어도 2개의 문제가 발생하고 있다.
VIPER에서 Router 는 자기 자신 내부에서 각 뷰로 넘어가기 위한 로직을 소화한다.
필요한 만큼의 의존성 주입이 이루어진다.
그러나 MVVM은 뷰를 어떻게 넘기고 보여줄지를 담당하는 Router가 없기에 아래와 같은 문제가 발생한다.

  1. 불필요한 의존성 발생 :
    • 대체 하나의 뷰컨이 몇 개의 뷰컨과 관계를 맺고 있는 것인가.
  2. 테스트 용이성 저하 :
    • 뷰 모델 테스트를 위해 뷰 컨을 stub해야 한다.
  3. 재사용성 저하
  4. 유지보수 어려움

4.3. MVVM의 한계 극복

Router가 없다면, 만들면 된다.
이를 위한 Flow Coordinators 패턴이 준비되어 있다.
이제 MVVM에서도 하나의 뷰 컨트롤러를 이곳저곳에서 더 편하게 재활용할 수 있다.
위에서 나온 코드를 이제 다 지워버리고 이렇게 해보자.
이제 함수를 통해서 의존성을 필요한 만큼만 주입할 것이다.

class MyViewController {
  let onDone = (Void -> Void)?

  func doneButtonTapped() {
    onDone?(prepareNeccesaryState())
  }
}

뷰컨, 뷰모델은 다른 화면을 참조하지 않을 것이고, UIKit 뷰를 그리는 객체나 메소드를 쓸 필요가 없어진다.
Delegate 처럼 작동할 수 있을 것이며, 싱글톤 참조도 필요하지 않다.
테스트 코드도 아래처럼 간단해질 것이다.

let vc = createVC()
var executed = false
vc.onDone = {
  executed = true
}
//! add code here to trigger done state
expect(executed).toEventually(beTruthy())

실제로 어떻게 뷰컨들의 흐름을 제어하면서 필요한 화면으로 딱딱 넘길 수 있을까.
이를 위해 Flow Coordinators 패턴을 제안한다.


5. Flow Coordinators

이 패턴은 FlowController를 활용하며 세 가지 역할을 갖고 있다.

  1. 특정 상황에 맞는 뷰 컨트롤러를 설정한다.
    • 예시 : 게시글 화면에서의 ImagePicker와 프로필 사진 변경 화면에서의 ImagePicker를 서로 다르게 보여주기
  2. 각 뷰 컨트롤러에서의 중요한 이벤트를 감시하고 활용하여 각 뷰 컨트롤러 간의 흐름을 조정한다.
  3. 뷰 컨트롤러가 자기 자신의 역할을 위해 필요한 객체를 제공하여 뷰 컨트롤러 내부의 싱글톤 남용을 방지한다.

마치며

사실 Coordinator 패턴은 꽤 오래된 패턴이라고 한다.
지금까지 한 번도 들어본 적이 없다니.. 나는 아직 갈 길이 멀다.

자세한 Coordinator 패턴에 대해 알고 싶다면 글의 서두에서 밝힌 출처로 가거나, 이 블로그를 보면 좋다.

220809

profile
🦶🏻🦉(발새 아님)

0개의 댓글