본문 바로가기

Software architecture and design pattern/iOS architecture pattern

[iOS architecture] Swift에서 사용되는 MVC 아키텍쳐 탐구하기 | app architecture pattern vs design pattern

 

이번 포스트를 통해 MVC architecture에 대해, 알고있던 개념들을 정리하려고 합니다. original mvc pattern, cocoa mvc pattern, realistic cocoa mvc pattern 세 가지에 대해서 다룰려고 합니다. 그 전에 App's architecture vs design pattern에 대해서 알고 가면 좋을 것 같습니다.

 

1. App's architecture pattern vs design pattern

App architecture pattern은 iOS 앱 전체의 구조와 구성 요소 간의 관계 설계, 구상합니다. 대표적으로 MVC, MVVM, VIPER, clean architecture등이 있습니다. 아키텍쳐 패턴을 활용한 개발은 유지보수도 쉽고, 의존성 관리 등의 향상에 좋습니다. 왜 사용하냐면 유지보수가 간편해지기 때문입니다. 일정한 틀, 규격을 지정하고 각각의 틀에 맞는 성향의 object, func, protocol 등을 구성한다면 훨씬 보기도 쉽고, 수정하기도 쉽기 때문입니다.

 

유지보수...? 간단한 예로 어두운 서럽 안에 빨, 주, 노 3가지 타입의 색이 있는 사탕들이 무수히 많이 있다고 가정하겠습니다. 내가 원하는 특정 색의 사탕을 전부 찾기 위해 계속 뒤척이는 방법이 있습니다. 이 방법보다 서랍 안에 사탕이 놓일 영역 규칙을 정의하고 서랍안에 있는 모든 사탕들을 뺀 후 규칙에 맞게 놓으면 됩니다. 1번째 구역은 빨간색 사탕들, 2번째 영역은 주황색 사탕들, 3번째 영역은 노란색 사탕들로 놓아진다면 찾기 훨씬 쉽기 때문입니다. 나 혼자만 찾기 쉬운게 아니라 내 규칙을 타인에게 알려주면, 다른 사람들도 찾기 쉽기 때문입니다:)

 

Design pattern은 앱을 설계하면서 발생되는 여러 문제들을 손쉽게 다루기 한 패턴입니다. 중복되는 코드를 새로운 패턴으로 하여 범주로 묶거나, 조직화해서 유지보수하기 쉬운 코드를 제공할 수 있습니다. 객체, 클래스의 구조, 상호작용에 대해서 더 좋은 방법을 패턴화 한 것입니다. 공통적으로 발생되는 문제를 해결하는 방법을 표준적으로 제공하는데, 대표적으로 GoF(Gang of Four) 디자인 패턴이 있습니다. factory, singleton, observer, strategy 등 여러 패턴이 있습니다.

 

2. MVC architecture pattern concept in swift

MVC는 1970년대 Smaltalk(: 객체 지향 프로그래밍 Object-oriented programming)언어와 같이 등장했습니다. 이를 도입한 많은 objects는 좀 더 재사용성이 높아지고, 인터페이스를 잘 정의할 수 있는 경향이 있습니다. + 확장성까지!!

MVC 패턴은 Model, View, Controller 세 Objects의 타입들로 나누는 것입니다. 이 그룹 중 하나에 속하는 object를 선택하거나, 포함되도록 custom class를 만들며 앱을 개발하는 것입니다.

 

Model, View, Controller? 요약하자면, View는 model의 데이터를 바탕으로 만들은 UI component를 화면에 보여줍니다. 이때 사용자의 터치 이벤트는 Controller에서 담당하고 새로운 형태의 style을 뷰에게 업데이트 하라고 알려주던가, 데이터를 변경하라구 model에게 알려줍니다. 끝!!

 

 

Model layer

앱에서 사용되는 중요한 데이터가 이곳에 model object로 캡슐화되어 있어야 합니다. 또한 데이터를 처리하는 방식에 대한 로직도 포함됩니다. 앱에서 어느 데이터가 화면에 로드되려면 반드시 model object를 거쳐야 합니다. (화면에 로드되기 위한 데이터는 model에서 보유하기 때문입니다.) 하지만 model object가 화면의 presentation에 관여하지는 않습니다. (모델이 화면을 그리라는 명령 등을 하지는 않습니다.)

 

  • dao(data access object curd를 ). on disk, core data, realm CURD 로직
  • 데이터 유효성 검사 로직
  • Constraints
  • 네트워크에서 파싱받아지는 객체
  • (network layer like encoding, decoding etc.. 하지만 model이 너무 커지기에 따로 network layer로 분리하지만, 반드시 MVC 중에 포함되어야 한다면 model layer에 속합니다.)

 

View layer

앱에서, 사용자와 상호작용하는 화면의 모든 부분은 view layer에 속합니다. 재사용할 수 있어야 합니다. Business logic이 속하면 안됩니다. 모델의 데이터를 표시하는 방법에 대해서 알고 있지만, 모델의 데이터가 어느 방식으로 변경되는지 세부적인 코드는 모릅니다. 

 

  • 일반적으로 UIView 상속
  • animation, core animation, core graphics 등

 

뷰에서 아래의 조건에 대한 경우가 발견되면 즉시 리펙토링을 해야합니다...

 

  • Does it interact with the model layer?
  • Does it contain any business logic?
  • Does it try to do anything not related to UI?

 

Controller layer

Model과 view를 분리했지만 view에서 데이터를 바탕으로 화면에 보여지기 위해선 데이터가 필요하고, 이는 model에서 관리하고 있음으로 model과 view사이의 중재자 역할을 합니다. 뷰에서 표시해야 할 데이터를 모델 객체에서 access하고, 뷰에서 발생한 사용자의 interaction을 통해 모델이 변경해야 할 때 업데이트합니다. 이 뿐 아니라 여러 개체의 생명 주기도 관리해야 하고, 응용 프로그램에 대한 설정, 조정 작업을 해야 합니다.

 

 

Combining Roles

예를 들어 object가 controller와 view의 역할을 모두 수행하도록 할 수 있고 이를 ViewController라고 합니다. Controller가 view(interface)를 소유합니다. 그래서 인터페이스를 관리하고, model과 통신합니다. 이와 같은 방식으로 ModelController object도 결합될 수 있습니다. 주로 model을 소유하고 view 개체와 통신합니다. 

 

MVC as a compound pattern ( original concept )

 

https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/Model-View-Controller/Model-View-Controller.html#//apple_ref/doc/uid/TP40010810-CH14-SW1

 

  • Composite : view object는 사용자의 interaction에서 input을 담당하고 view hierarchy로 작동됩니다. ( 시각적 측면 유지 )
  • Strategy : Controller object는 하나 이상의 view object에 대한 전략을 구현합니다. view의 특정 인터페이스 update는 전부 Controller가 결정합니다. + model 업데이트
  • Observer : 일반적으로 view object에게 상태 변경 사항을 알려줍니다. 

 

초창기 smalltalk에서 사용된 MVC 패턴은Composite, Strategy, Observer 패턴으로 구성되었습니다. 중요한 것은 View와 Model간의 관계에서 notify, state change를 하도록 되어 있습니다.

 

이 패턴을 적용하자면, 결국 View는 model을 own하고 있지 않기 때문에, model은 view에게 notify를 하기 위해 바인딩을 해야합니다. 특정 model 내부 변수가 50개라면..? 이 뿐 아니라 뷰를 다른 뷰로 교체할 때도 model에서 바인딩을 새로 다 변경해야 한다는 사실...

 

apple에서 디자인한 cocoa MVC 패턴은 original mvc pattern과 다소 다릅니다. original mvc pattern을 사용한다면 재사용성이 향상되지 않습니다. 아래 그림은 Cocoa framework의 특성에 맞게 개발된 패턴입니다.

 

Cocoa version MVC

 

https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html

 

여기서 중요한 것은 Controller가 Model과 View객체 사이의 데이터 흐름을 조정합니다. 그런데.. 그림을 자세히보면? model은 Controller에게 notify를 해야 합니다. Notify를 하기 위해선, Model이 Controller에게 바인딩을 해야합니다. 그래야만 model의 상태 변화를 Controller에게 notify할 수 있습니다.

 

model이 컨트롤러에게 바인딩?..

 

바인딩할 수 있는 방법을 생각했는데, 크게 KVO, NotificationCenter 사용, didSet을 이용한 Observer, @Publihsed를 생각했습니다. 만약 특정 model의 내부 인스턴스가 40개라면? 이 모든 인스턴스 각각에 바인딩 처리는 어려울 것입니다.

 

@Published let dataSource = Model(.................)

 

@Publihsed를 사용한다면 model 인스턴스에 @Publihsed를 사용할 때, model 인스턴스 자체를 바인딩함으로 특정 데이터가 업데이트 될 때마다 알려줄 수 있습니다. 그러나 정확히 어느 데이터가 변경됬느지 몰라 전체적으로 데이터들을 갱신한다면? 리로드 과부화가 올 것입니다.

 

어떻게 많은, 다량의 데이터 각각의 변화를 notify 해야할지 고민입니다... 여러 책을 봤고 udemy 등 강의에서도 MVC 패턴을 사용할 때, model이 Controller에게 바인딩을 하지는 않습니다. 바인딩 대신 소유 합니다. 그 그림은 아래와 같습니다.

 

Realistic cocoa MVC 

 

 

UIKit의 일반적인 MVC에서 정말 간단하게 말하자면,

뷰의 역할 : 화면에 보이는 모든 UI component를 의미합니다. 

모델의 역할 : 화면에 보일 데이터. 특정 화면의 UI component가 보여주기 위한 데이터 담당합니다.

컨트롤러의 역할 : 뷰에서 발생하는 이벤트 감지 및 처리(UIResponder), 뷰 업데이트, 뷰의 component가 필요한 데이터를 뷰에게 전달, 데이터 업데이트, 업데이트 된 model의 데이터를 Controller가 했으니 업데이트 된 데이터에 관련된 View 업데이트,  view의 ViewController에 속한 view의 lifecycle 담당, animation 처리, view의 layout 담당, network layer가 model layer에서 분리된다면 서버 관련 resopnd, request 로직 처리 등.. (역할이 좀 많네요)

 

UIViewController 클래스

 

위 realistic cocoa mvc 사진과 같이 UIViewController는 자동으로 View를 Own하고 있습니다. 그래서 View | Controller가 붙어있는 상태로 그려져 있습니다. 물론 UIViewController를 상속한 ViewController는 model의 각각의 인스턴스를 Controller에 바인딩 하기보단, 소유하는것이 편하기 때문에 ViewController는 model을 소유합니다.

 

Controller와 View는 결합되어 있고, model까지 소유하게 된 UIViewController 타입의 ViewController를 볼 수 있습니다. 모델, 뷰, 뷰와 상호작용하는 delegate로직 등 여러 역할을 담당하게 ViewController가 담당하게 되면서 Massive ViewController라는 농담이 생겼습니다. ViewController가 커진다는 것은 코드 길이가 늘어난다는 것이고, 유지보수가 힘들어짐과 동시에 유닛 테스트가 어려워집니다.

 

하지만 위에서 언급한 여러 디자인 패턴을 사용해서 UIView의 컴포넌트 분리,  datasource, delegate를 따로 분리 등을 한다면? ViewController의 부담을 많이 줄일 수 있을 것 같습니다. 

 

MVVM, VIPER 등의 디자인 패턴은 MVC 패턴을 보완한 아키텍쳐 패턴입니다.

 

 

 

References

https://www.kodeco.com/1000705-model-view-controller-mvc-in-ios-a-modern-approach

 

https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/Model-View-Controller/Model-View-Controller.html#//apple_ref/doc/uid/TP40010810-CH14-SW1