티스토리 뷰

반응형

이번 글에서는 여러 가지 디자인 패턴 중 iOS 앱 개발에 적용되는 MVC(Model-View-Controller)에 대해 알아보려고 한다.

 

Apple Developer Document - Model-View-Controller

Model-View-Controller

Model-View-Controller(MVC) 디자인 패턴은 꽤 오래되었다. 이는 글로벌 아키텍처와 관련되어있고 일반적인 역할에 따라 객체를 분류한다는 점에서 높은 수준의 패턴이다. 객체 지향 프로그램에서 MVC 디자인 패턴을 적용하면 여러 가지 장점이 있다. 객체 지향 프로그램에서 객체는 재사용 가능성이 높고 서로의 상호작용이 잘 정의되어있다. 이는 MVC 디자인 패턴을 기반으로 하는 프로그램이 변화하는 요구사항에 잘 적응할 수 있다는 것이다. 게다가 Cocoa의 바인딩, 문서 아키텍처, 스크립팅 가능성과 같은 기술이 MVC를 기반으로 하며 사용자 정의 객체는 MVC에서 정의한 역할 중 하나를 수행해야 한다.


Roles and Relationships of MVC Objects

MVC 디자인 패턴은 Model, View, Controller의 세 가지 타입의 객체가 있다고 간주한다. MVC 디자인 패턴은 이러한 타입의 객체가 프로그램 및 통신에서의 각자의 역할을 정의한다. 프로그램을 디자인할 때 중요한 단계 중 하나는 어떤 객체를 어떠한 타입으로 볼 것인지 선택하는 것이다. 세 가지 타입의 객체는 추상 경계를 통해 각자 다른 타입의 객체와 분리되며 이러한 경계를 넘어 다른 타입의 객체와 통신한다.

 

Model Objects Encapsulate Data and Basic Behaviors

Model 객체는 특별한 지식 또는 전문성을 나타낸다. 이들은 프로그램의 데이터와 데이터를 조작하는 논리를 정의한다. 잘 만들어진 MVC 프로그램에서는 모든 중요한 데이터가 모델 객체 안에 캡슐화되어있다. 데이터가 프로그램에 로드되면 데이터가 재사용될 가능성이 있기 때문에 프로그램의 실행 중에는 계속 모델 객체에 있어야 한다.

 

모델 객체가 이를 보여주고 수정할 수 있는 사용자 인터페이스와 명시적으로 연결되어있지는 않다. 예를 들어 주소록과 같은 사람을 나타내는 모델 객체가 있다고 할 때 생년월일을 저장할 수 있고 Person 모델 객체에 저장하는 것이 좋을 것이다. 하지만 생년월일이 저장되는 타입이 문자열인지 다른 자료형인지와 같은 기타 정보는 다른 곳에 저장하는 것이 더 좋을 것이다.

 

실제로는 이러한 분리가 항상 좋은 것은 아니지만 일반적으로는 모델 객체는 인터페이스와 표현 문제와 관련이 없어야 한다. 예외는 그래픽을 나타내는 모델 객체가 있는 그리기 응용 프로그램이다. 그래픽 객체가 그리는 방법을 알아야 하는 이유는 시각적인 것을 정의하는 것이기 때문이다. 하지만 이러한 경우에도 그래픽 객체는 특정 시점에 의존해서는 안되며 언제 그려야 하는지 알 필요는 없다. 이는 그래픽 객체는 뷰 객체에 의해 그림을 그리도록 요청을 받아야 한다는 것이다.

 

View Objects Present Informations to the User

View 객체는 프로그램에서 모델의 데이터를 표시하는 방법을 알고 있으며 사용자가 편집하도록 허용할 수 있다. 뷰는 현재 보이고 있는 데이터를 저장하는 책임이 없어야 한다. 뷰 객체는 하나의 모델 객체의 일부나 전체, 혹은 여러 개의 모델 객체를 보여줄 수 있다. 이러한 뷰에는 다양한 종류가 있다.

 

뷰 객체는 재사용이 될 가능성이 있으며 프로그램 간에 일관성을 제공한다. Cocoa에서 AppKit 프레임워크는 많은 수의 뷰를 정의하고 이를 인터페이스 빌더 라이브러리에서 제공한다. AppKit의 NSButton과 같은 뷰 객체를 재사용하면 프로그램에서 버튼이 다른 Cocoa 프로그램에서의 버튼처럼 작동하도록 보장하여 높은 수준의 일관성을 보장한다.

 

뷰는 모델을 올바르게 보여주고 있는지도 확인해야 한다. 따라서 모델 객체에 변화가 있는 것을 알아야 하는데 모델 객체는 특정 뷰 객체에 연결되면 안 되기 때문에 이러한 변화를 알리는 일반적인 방법이 필요하다.

 

Controller Objects Tie the Model to the View

Controller 객체는 프로그램의 뷰 객체와 모델 객체 사이의 중개자 역할을 한다. 컨트롤러는 뷰가 보여줘야 하는 모델 객체에 접근할 수 있는지 확인하고 모델 객체의 변경 사항을 뷰 객체에 알리는 통로 역할을 한다. 컨트롤러 객체는 프로그램에 대한 설정 및 조정 작업을 수행하고 다른 객체의 수명 주기도 관리할 수 있다.

 

일반적인 Cocoa MVC 디자인에서 사용자가 값을 입력하거나 뷰 객체를 통해 선택하면 해당 값 또는 선택이 컨트롤러 객체에 전달된다. 컨트롤러 객체는 프로그램마다 특정한 방식으로 이러한 입력을 해석하고 해당 입력으로 수행할 작업을 모델 객체 또는 뷰 객체에 지시할 수 있다. 반대로 모델 객체가 변경되는 경우엔 변경 사항을 컨트롤러 객체에 전달한 뒤 해당 모델의 변화에 영향을 받는 뷰 객체가 변화에 따라 업데이트하도록 요청한다.

 

컨트롤러 객체는 재사용이 가능할 수도 불가능할 수도 있다. Cocoa 컨트롤러 객체 타입은 Cocoa의 다양한 컨트롤러 객체 타입을 나타낸다.

 

Combining Roles

각 객체가 수행하는 MVC 역할을 병합하여 하나의 객체가 여러 역할을 수행하게 만들 수도 있다. 만약 뷰 객체와 컨트롤러 객체의 역할을 모두 수행하는 객체라면 이를 뷰 컨트롤러 객체라고 한다. 물론 모델 컨트롤러도 존재할 수 있다.

모델 컨트롤러는 주로 모델 계층과 관련된 컨트롤러이다. 이는 모델을 소유하며 주된 역할은 모델을 관리하고 뷰 객체와 통신하는 것이다. 모델 전체에 적용되는 Action 메서드는 일반적으로 모델 컨트롤러에서 구현된다. Document 아키텍처는 이러한 메서드들을 제공하는데 예를 들어 NSDocument 객체는 파일 저장과 관련된 메서드를 자동으로 처리한다.

뷰 컨트롤러는 주로 뷰 레이어와 관련된 컨트롤러이다. 이는 인터페이스(뷰)를 소유하며 주된 역할은 인터페이스를 관리하고 모델과 통신하는 것이다. 뷰에 보이는 데이터와 관련된 Action 메서드는 일반적으로 뷰 컨트롤러에서 구현된다. NSWindowController 객체와 같은 것이 뷰 컨트롤러의 예이다.

 

조금 후에 나올 Design Guidelines for MVC Application 섹션에서 MVC 역할이 병합된 객체와 관련된 몇 가지 디자인 팁을 알아보도록 하자.


Types of Cocoa Controller Objects

컨트롤러 객체 모델을 뷰에 연결하면 컨트롤러 객체의 추상적인 아웃라인이 그려지지만 실제로는 더욱 복잡하다. Cocoa에는 Mediating Controller(중재 컨트롤러), Coordinating Controller(조정 컨트롤러)라는 두 가지 일반적인 컨트롤러 객체가 있다. 각각의 컨트롤러 객체는 다른 클래스들과 연결되어 있으며 각각 다른 범위의 동작을 제공한다.

 

Mediating 컨트롤러는 일반적으로 NSController 클래스에 상속된 객체이다. 중재 컨트롤러 객체는 Cocoa 바인딩 기술에서 사용되며 이는 뷰 객체와 모델 객체 간의 데이터 흐름을 촉진하거나 중재한다. AppKit은 NSController 클래스와 서브 클래스 들을 구현해 뒀는데 이런 클래스와 바인딩 기술은 iOS에서는 사용할 수 없다. 중재 컨트롤러는 일반적으로 인터페이스 빌더 라이브러리에서 드래그하는 ready-made 객체이다. 이러한 객체를 구성하여 뷰 객체의 프로퍼티와 컨트롤러 객체의 프로퍼티 사이에 바인딩을 설정 한 뒤 컨트롤러 객체의 프로퍼티와 모델 객체의 프로퍼티와 바인딩을 설정할 수 있다. 결과적으로 사용자가 뷰 객체에 표시된 값을 변경하면 새 값이 자동으로 매개 컨트롤러 객체를 통해 모델 객체에 전달되어 저장된다. 또한 모델 객체의 프로퍼티 값을 변경하면 변경사항이 뷰 객체에 전달된다.

 

Coordinating 컨트롤러는 일반적으로 NSWindowController, NSDocumentControllerobject(AppKit에서만 사용 가능) 또는 NSObject의 커스텀 서브클래스이다. 프로그램에서의 역할은 전체 프로그램 또는 nib 파일에서 보관되지 않은 객체와 같은 일부 기능을 관장하거나 조정하는 것이다. 조정 컨트롤러는 다음과 같은 서비스를 제공한다.

  • 델리게이션 메시지에 대한 응답 및 알림 관찰
  • Action 메시지에 응답
  • 속해있는 객체의 수명주기 관리
  • 개체 간의 연결 설정 및 기타 설정 작업 수행

NSWindowController, NSDocumentController은 문서 기반 프로그램을 위한 Cocoa 아키텍처의 일부인 클래스이다. 이러한 클래스의 인스턴스는 위에 나열된 서비스들에 대한 기본 구현을 제공하고 프로그램 별 동작을 구현하기 위해 하위 클래스를 만들 수도 있다. NSWindowController 객체를 사용하여 문서 아키텍처를 기반으로 하지 않는 프로그램에서 window를 관리할 수도 있다.

 

조정 컨트롤러는 nib 파일에 보관된 객체를 자주 소유하게 된다. 파일의 소유자로서 조정 컨트롤러는 nib 파일의 객체 외부에 있고 이러한 객체를 관리한다. 이렇게 소유한 객체들에는 중재 컨트롤러, window 객체 및 뷰 객체가 포함된다. 자세한 내용은 잠시 뒤에 MVC as a Compound Design Pattern 섹션에서 알아보도록 하자.

 

NSObject의 커스텀 서브클래스의 인스턴스들은 조정 컨트롤러에 적합하다. 이러한 컨트롤러 객체는 조정, 중재를 위한 함수들을 결합한다. 중재를 위한 동작으로는 target-action, outlets, delegation, notification을 통해 뷰 객체와 모델 객체에서의 데이터 이동을 용이하게 한다. 이러한 코드는 프로그램마다 다르기 때문에 재사용이 보통은 불가능하다.


MVC as a Compound Design Pattern

MVC는 몇 가지 기본적인 디자인 패턴으로 구성된 디자인 패턴이다. 이러한 기본적인 패턴이 함께 작동하여 MVC 프로그램의 특징인 기능 분리와 통신 경로를 정의한다. 하지만 MVC의 전통적인 개념은 Cocoa가 지정하는 것과는 다른 기본 패턴을 지정하며 주된 차이점은 프로그램에서 뷰 객체와 컨트롤러 객체에 주어진 역할에 있다.

 

원래 Smalltalk 컨셉에서는 MVC는 Composite, Strategy, Observer 패턴으로 구성된다.

 

Composite(합성)

프로그램의 뷰 객체는 실제로는 뷰 계층 구조로 함께 작동하는 중첩된 보기의 합성된 형태이다. 이러한 디스플레이 구성요소는 테이블 뷰와 같은 복합적인 뷰와 버튼과 같은 개별적인 뷰까지 다양하다. 사용자의 입력 및 표시는 Composite 구조의 모든 수준에서 발생할 수 있다.

 

Strategy(전략)

컨트롤러 객체는 하나 이상의 뷰 객체에 대한 전략을 구현한다. 뷰 객체는 시각적 측면을 유지하는데 제한되며 인터페이스 동작은 모드 컨트롤러에게 위임한다.

 

Observer(옵저버)

모델 객체는 상태 변경에 대한 알림을 받아 변화에 대해 관심 있는 객체들을 유지한다.

전통적인 MVC 방식

위의 그림은 Composite, Strategy, Observer 패턴이 함께 동작하는 MVC의 전통적인 방식이다. 사용자가 Composite 구조의 일정 수준에서 뷰를 조작하고 이벤트를 발생시키면 컨트롤러 객체가 이벤트를 수신하고 이를 프로그램에 따라 자체적으로 해석한다. 즉 Strategy를 적용하는 것인데 이를 통해 모델 객체에 요청하여 상태를 변경하거나 뷰 객체의 동작, 모양 등을 변경하도록 요청할 수 있다. 이렇게 모델 객체의 상태가 변경되면 Observer로 등록한 모든 객체에 알리게 되고 만약 이러한 객체가 뷰 객체였다면 그에 따라 모양을 업데이트할 수 있다.

더보기

참고로 뷰 객체가 사용자의 입력과 선택을 전달할 수 있도록 하는 target-action 메커니즘은 coordinating, mediating 컨트롤러 모두에서 구현될 수 있다. 하지만 각각의 컨트롤러 타입에 따라 메커니즘의 디자인은 차이가 있는데 먼저 Coordinating 컨트롤러의 경우 인터페이스 빌더에서 뷰 객체를 target에 연결하고 특정 signature을 준수해야 하는지 action selector를 지정한다. 창과 전역 프로그램 객체의 대리자이기 때문에 coordinate 컨트롤러도 응답 체인에 있을 수 있다. Mediating 컨트롤러에서 사용하는 바인딩 메커니즘은 뷰 객체를 target에 연결하고 임의 유형의 다양한 매개 변수를 사용하여 action signature를 허용한다. 하만 Mediating 컨트롤러는 응답 체인에 있지 않는다.

복합 패턴인 MVC의 Cocoa 버전은 위에서 본 기존 버전과는 유사점이 있고 실제로 위의 버전처럼 작동하는 프로그램을 구성하는 것도 가능하다. 여기서 바인딩 기술을 사용하면 뷰가 모델 객체를 직접 관찰하여 상태 변경 알림을 받는 Cocoa MVC 프로그램을 쉽게 만들 수 있다. 하지만 이 디자인에는 이론적인 문제가 있는데 뷰 객체와 모델 객체는 프로그램에서 재사용이 가능한 객체여야 한다는 것이다. 뷰 객체는 OS와 시스템이 지원하는 "look and feel"을 나타낸다. 이러한 모양과 행동의 일관성은 필수적이기 때문에 재사용이 가능한 객체가 필요하다. 모델 객체는 정의가 어떻게 되었냐에 따라 도메인과 관련된 데이터를 캡슐화하고 해당 데이터에 대한 작업을 수행한다. 디자인 측면에서는 모델과 뷰 객체를 서로 분리하여 유지하는 것이 재사용 가능성이 향상되기 때문에 가장 좋다.

 

Cocoa 에서의 MVC

따라서 기존의 방법이 아닌 Cocoa에서의 MVC는 위와 같은 구조를 따르게 된다. 위의 그림은 두 가지 기본 디자인 패턴이 더 포함되어 있지만 훨씬 깔끔해 보이는 구성을 보여준다. 위의 디자인 패턴에서 컨트롤러 객체는 Strategy와 Mediator 패턴을 통합하여 모델과 뷰 객체 사이의 데이터 흐름을 양 방향으로 중재하게 된다. 모델의 변경 사항은 컨트롤러 객체를 통해 뷰 컨트롤러로 전달된다. 또한 뷰 객체는 target-action 메커니즘의 구현을 통해 Command 패턴을 통합한다.

 

Mediator 디자인 패턴과 관련하여 위의 그림의 복합 디자인 패턴으로 수정된 실질적인 이유가 있다. Mediating 컨트롤러는 NSController의 구체적인 서브클래스에서 파생되며 이러한 클래스는 Mediating 패턴을 구현하는 것 외에도 프로그램에서 활용해야 하는 여러 가지 기능을 제공한다. 만약 바인딩 기술을 사용하지 않겠다고 하면 뷰 객체는 Cocoa 알림 센터와 같은 메커니즘을 사용하여 모델 객체에서 알림을 받을 수 있다. 하지만 이렇게 하기 위해서는 모델 객체가 게시한 알림에 대한 정보를 추가하기 위해 커스텀 뷰 서브클래스를 만들어야 한다.

 

잘 설계된 Cocoa MVC 프로그램에서 Coordinating 컨트롤러는 nib 파일에 보관된 Mediating 컨트롤러를 소유할 때도 있다. 위의 그림은 두 유형의 컨트롤러 객체 간 관계를 보여준다.


Design Guidelines for MVC Applications

다음은 MVC를 사용하는 프로그램의 설계에서의 고려사항에 대한 가이드라인이다.

 

  • NSObject의 커스텀 서브클래스의 인스턴스로 mediating 컨트롤러를 사용할 수도 있지만 이것이 모든 작업을 수행할 필요는 없다. Cocoa 바인딩 기술을 위해 NSController 객체에서 제공하는 NSObjectController, NSArrayController, NSUserDefaultController, NSTreeController의 인스턴스 또는 이러한 구체적인 NSController의 서브 클래스 중 하나를 커스텀하여 사용한다. 하지만 프로그램이 매우 간단하고 outlet, target-action을 사용한 중개 동작을 구현하는 글루 코드를 작성하는 게 더 편하다면 NSObject의 서브클래스를 mediating 컨트롤러로 사용하면 된다. NSObject의 커스텀 서브클래스는 key-value 코딩, key-value observing, 편집기 프로토콜을 사용하여 NSController 의미에서 mediating 컨트롤러를 구현할 수도 있다.
  • 객체에서 MVC 역할을 합칠 수도 있지만 가장 좋은 것은 역할 간에 분리를 유지하는 것이다. 이러한 분리는 재사용성과 객체가 사용되는 프로그램의 확장성을 향상시킨다. 클래스에서 MVC역할을 병합하려는 경우 해당 클래스의 주요 역할을 선택한 뒤 동일한 구현 파일을 사용하여 해당 클래스의 다른 역할을 확장하면 된다.
  • 잘 설계된 MVC 프로그램의 목표는 재사용 가능한 객체를 가능한 많이 사용하는 것이다. 특히 뷰 객체와 모델 객체는 재사용성이 높아야 한다. 그렇게 하여 프로그램 별 동작은 컨트롤러 객체에 가능한 많이 집중되게 만드는 것이다.
  • 뷰가 모델을 직접 관찰하여 변화를 감지할 수도 있지만 그렇게 하지 않는 것이 가장 좋다. 뷰 객체는 항상 컨트롤러 객체를 거쳐서 모델 객체의 변화를 아는 것이 좋은데 그 이유는 다음과 같다.
    • 바인딩 메커니즘을 사용하여 뷰 객체가 모델 객체의 속성을 직접 관찰하게 하는 경우 NSController 및 하위 클래스가 프로그램에 제공하는 모든 이점을 무시하게 된다.
    • 바인딩 메커니즘을 사용하지 않는 경우 기존 뷰 클래스를 서브 클래싱 하여 모델 객체가 게시한 변경 알림을 관찰하는 기능을 추가해야 한다.
  • 프로그램 플래스에서 코드 종속성을 제한하도록 노력해야 한다. 클래스가 다른 클래스에 대한 종속성이 클수록 재사용 가능성이 낮아진다. MVC 역할에 따라 권장사항은 달라지는데 이는 다음과 같다.
    • 뷰 클래스는 모델 클래스에 종속돼서는 안 된다. (커스텀 뷰에서는 불가피한 경우가 있을 수 있음)
    • 뷰 클래스는 mediating 컨트롤러 클래스에 종속되면 안된다.
    • 모델 클래스는 다른 모델 클래스 이외의 것에 종속되면 안된다.
    • Mediating 컨트롤러 클래스는 모델 클래스에 종속되면 안된다. (커스텀 컨트롤러에서는 불가피한 경우가 있을 수 있음)
    • Mediating 컨트롤러 클래스는 뷰 클래스나 Coordinating 컨트롤러 클래스에 의존해서는 안된다.
    • Coordinating 컨트롤러 클래스는 모든 MVC 타입의 클래스에 종속된다.
  • Cocoa가 프로그래밍 문제를 해결하는 아키텍처를 제공하고 이 아키텍쳐가 MVC 역할을 특정 객체에 할당하는 경우 아키텍쳐를 사용하는 편이 프로젝트를 구성하는데 더 쉬울 것이다. 예를 들어 Document 아키텍처에는 NSDocument 객체를 파일의 소유자로 구성하는 Xcode 프로젝트 템플릿이 포함되어있다.
반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/03   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함