MVC와 Cocoa MVC

J.Noma·2021년 10월 24일
0

원문 : Model-View-Controller

정말 기절할 정도로 길다...


MVC 디자인 패턴은 꽤 오래된 개념이며 이것의 변형은 초기 smalltalk때부터도 얘기가 나왔습니다

MVC는 디자인 패턴이긴 하지만 app 전반적으로 아키텍쳐와 객체역할을 정의한다는 점에서 high-level 패턴이라고 볼 수 있습니다. 또한, 몇가지 기본 패턴들이 합쳐진 compound 패턴입니다

OOP 설계를 할 때, MVC 패턴을 쓰면 좋은 점이 있습니다. MVC로 설계하면 프로그램의 많은 객체들이 좀 더 재사용가능해지며 인터페이스가 더 잘 정의됩니다. 즉, 더 쉽게 extensible해진다고 표현할 수 있습니다

Cocoa의 많은 기술과 아키텍쳐가 MVC에 기반하므로 당신이 custom 객체를 만들더라도 MVC에 의해 정의되는 role 중 하나를 따라야 합니다


👨‍👨‍👦 MVC 객체들의 역할과 관계

MVC 디자인 패턴은 3가지 타입이 있습니다: model / view / controller

MVC 패턴은 각 객체들이 어떤 타입인지와 객체들 간 소통방법을 정의합니다
그러므로 app을 설계할 때, 주요한 step 중 하나가 각 객체들이 3가지 타입 중 어느 그룹에 속할지를 정하는 것입니다

각 타입은 추상적인 경계에 의해 분리되며 이 경계를 넘나들며 소통합니다
(what is abstract boundary?)

✔️ Model: data 캡슐화 / 기본 동작

model은 app의 data를 간직하고 이 data들을 조작하는 로직을 정의합니다
잘 설계된 MVC기반 app은 model의 모든 중요 data를 캡슐화합니다

app의 영구적인 상태(초기설정 같은 것들)에 대한 어떤 data든지 해당 data가 app으로 한 번 load되면 model에 담겨야 합니다?
(Any data that is part of the persistent state of the application (whether that persistent state is stored in files or databases) should reside in the model objects once the data is loaded into the application.)

model은 특정 주제 영역에 관련한 정보들을 표현하므로 재사용가능한 경향이 있습니다
(Because they represent knowledge and expertise related to a specific problem domain, they tend to be reusable)

이상적으로, model은 유저 인터페이스와 명시적인 연결점이 없습니다
예로, person을 표현하는 model 객체가 있고 생일 정보를 저장하고 싶다면, 해당 person model 객체에 저장하는게 좋은 방법일 것입니다
하지만, 이렇게 data를 "저장"하는 것 외에 "날짜 string formatting"같은 것들은 아마 다른 곳에서 하는게 더 적절할 것입니다

실제로는, 이런 분리가 항상 최선의 방법은 아니고 융통성의 영역이 있긴 합니다만
일반적으로 model 객체는 인터페이스나 presentation issue와는 무관해야 합니다
이런 예외의 한 예시로, 그림그리는 app을 생각할 수 있습니다
이 app에서 graphics라는 객체는 그림 data이기도 하지만 그 자체로 visual적인 요소이기도 합니다
그래서 presentation에도 관여하게 하더라도 타당할 수 있습니다

하지만, 심지어 이런 경우조차도 graphics 객체가 특정 view와 직접적으로 연동되어서는 안됩니다. 그리고 "언제" present해야 할지에 대한 책임을 가지면 안됩니다
(보통 view의 버튼같은 것에 "언제"에 대한 책임을 줌)
model은 view로부터 draw를 요청받는 방식으로만 구현되어야 합니다

✔️ View: presentation

view는 model의 data를 어떻게 표시할지(가끔은 어떻게 edit을 허용할지)를 정의합니다
view는 data를 저장하는 것에는 책임이 없어야 합니다
(물론, data를 caching한다던지 성능 상의 이유로 유사한 trick을 쓰는 등의 예외도 있습니다)

하나의 view 객체가 한 model의 일부, 한 model 전체, 혹은 여러 model을 보여줄 수도 있습니다.

view 객체는 재사용가능하고 설정가능한 경향이 있습니다

그리고, 여러 app들에서도 일관성을 보입니다. Cocoa에서 AppKit 프레임워크는 많은 수의 view 객체들을 정의하고 이를 IB(Interface Builder) 라이브러리로 제공합니다
AppKit의 view 객체들(such as NSButton)을 재사용함으로써, 모든 Cocoa app에서 외형적으로나 기능적으로 높은 수준의 일관성을 보장합니다

view는 model을 실시간으로 정확하게 보여주어야 하므로 model의 값이 변경되는 것을 view가 알 필요가 있습니다
하지만 model이 특정 view와 직접적으로 연결되는 구조는 아니기 때문에, model이 변경되는 것을 알리기 위한 generic way가 view에게 필요합니다

✔️ Controller: Model과 View를 연결

controller 객체는 app의 view들과 model들 사이에서 중재자 역할을 합니다
controller는 가끔 view가 model에 접근할 수 있도록 보증하는 역할을 하고, view가 model의 변화를 알림받는 통로 역할을 합니다

또한, controller 객체는 app 전반적인 관점에서 set-up이나 task 스케줄링(coordinating tasks)을 할 수도 있고, 객체의 life cycle을 관리할 수도 있습니다

전형적인 Cocoa MVC 디자인에서, 유저가 view 객체를 통해 input하면 이것이 controller에게 전달됩니다
controller 객체는 아마 유저의 input을 app-specific한 방식으로 해석하여 model에게 알리거나 model의 프로퍼티를 변경합니다
어떤 controller는 유저가 같은 input을 반복할 때 view가 외형이나 기능을 변경하도록 말할 수도 있습니다.
(예로, 버튼을 쓸데없이 자꾸 누르면 disable시킨다던지)

반대로, model이 자신의 값이 변경될 때 view가 update하기를 기대하며 controller에게 알려주기도 합니다.

controller 객체는 "Cocoa에서 정의한 몇가지 controller타입"(아래에서 설명 예정) 중 어느 것이냐에 따라 재사용가능할 수도 있고 불가능할 수도 있습니다

✔️ 역할 합치기

누군가는 MVC의 role들을 병합할 수도 있습니다. 예로, controller와 view의 역할을 모두 수행하는 객체를 만드는 경우도 있습니다 (a.k.a viewController)
같은 방식으로, model-controller 객체도 만들 수 있습니다
경우에 따라 이렇게 role을 병합하는 것은 수용가능한 디자인입니다

Model Controller
model controller는 거의 model layer에 관련된 controller입니다?
(A model controller is a controller that concerns itself mostly with the model layer)
model controller는 model을 직접 소유하는데 그 주요한 책임은 model을 관리하고 view 객체와 소통하는 것입니다
model을 조정하는 action 메서드들을 보통 model controller에 구현합니다
document 아키텍쳐는 몇가지 메서드들을 제공합니다 (예로, document 아키텍쳐의 중심부인 NSDocument 객체는 파일 저장과 관련된 action 메서드들을 자동으로 관리합니다)

View Controller
view controller는 거의 view layer에 관련된 controller입니다
view controller는 view/인터페이스를 직접 소유합니다.
그리고, 주요한 책임은 이 인터페이스들을 관리하고 model 객체와 소통하는 것입니다
view로 보여줄 data와 관련한 action 메서드는 보통 view controller에 구현됩니다
(예로, 이것도 document 아키텍쳐의 부분 중 하나인 NSWindowController가 있습니다)


🎛 Cocoa에서 정의한 몇가지 Controller 타입

앞에서 controller가 model과 view 사이를 연결한다는 추상적인 윤곽 정도를 잡아보았습니다만, 실제로는 훨씬 복잡합니다

Cocoa에는 2가지 종류의 일반적인 controller 객체가 있습니다:
(1) mediating controller
(2) coordinating controller
각 종류는 서로 다른 class set과 연결되어 있으며 서로 다른 범위의 행동을 제공합니다

✔️ Mediating controller

mediating controller는 전형적으로 NSController class로부터 상속받는 객체로 Cocoa bindings 기술에 사용됩니다.
(bindings: view와 model을 묶어서(binding) 한쪽에서의 변경이 다른 쪽으로 자동 반영되게 하는 것)
medi. controller는 view와 model 간 data 흐름을 용이하게 하거나 중재합니다

iOS Note
AppKit은 NSController class와 그 subclass들을 구현합니다. 이 class들과 bindings 기술은 iOS에서는 사용불가합니다

medi. controller는 (보통은) 이미 만들어져 있는 객체로 Interface Builder 라이브러리에서 가져다 쓰면 됩니다.

당신은 이 medi. controller들을 "view 프로퍼티와 controller 프로퍼티" 간 혹은 "controller 프로퍼티와 model의 특정 프로퍼티" 간 bindings를 만드는데 사용할 수 있습니다
결과적으로 유저가 view에 있는 값을 변경할 때, 새로운 값이 medi. controller를 통해 자동으로 model로 전달됩니다.
그리고, model의 프로퍼티 값이 변경되면, view로 전달됩니다

추상 class인 NSController와 이를 오버라이딩하여 사용하는 subclass들(NSObjectController, NSArrayController 등)은 변화를 적용/폐기하는 능력이나 selection과 placeholder 관리같은 supporting feature들을 지원합니다
(추상 class: 직접 사용할 순 없고, 이를 상속받아 overriding하여 사용해야 하는 class)
(concrete subclass: 추상 class를 상속받는 subclass)

✔️ Coordinating controller

coordinating controller는 전형적으로 NSWindowControllerNSDocumentController 객체(AppKit에서만 가능), 혹은 customize를 위해 NSObject을 상속한 subclass의 인스턴스입니다

app에서 coord. controller의 역할은 app의 기능성을 감독/조정하는 것입니다
(예로, nib file로부터 보관되지 않은(unarchived) 객체 관리)
(nib file: iOS나 맥에서 유저 인터페이스를 저장하는 리소스 파일)

coordinating controller는 아래와 같은 서비스를 제공합니다

  • delegation 메시지에 응답
  • notification 감지하기
  • action 메시지에 응답하기
  • 소유한(owned) 객체의 life cycle 관리 (직접 해제시킨다던지)
  • 객체 간 연결점 만들기 (medi.가 하는 view <-> model간 연결 이외를 말하는 듯?)
  • 그 외 set-up task들 수행하기

대표적으로 NSWindowControllerNSDocumentController는 document-based app을 위해 Cocoa 아키텍쳐에 구현된 class들 중 일부입니다
이 class의 인스턴스는 위에 나열한 몇가지 서비스들을 위해 이미 default 구현을 해놓은 채로 제공합니다.
그리고, app에 맞게 좀 더 custom하고 싶으면 상속하여 subclass를 만들 수 있습니다
당신은 심지어 document-based가 아닌 app에서도 window를 관리하기 위해 NSWindowController를 사용할 수 있습니다

coordinating controller는 nib file에 저장된 객체를 소유하는 경우가 많습니다.
File의 오너로써, coordinating controller는 nib file의 객체 밖에 존재하면서 이 객체들을 관리합니다? (소유한다면서 왜 밖에 있다는거지?)
(A coordinating controller frequently owns the objects archived in a nib file. As File’s Owner, the coordinating controller is external to the objects in the nib file and manages those objects)
이 소유된 객체들은 window 객체나 view 객체같은 mediating controller를 포함합니다.
File의 오너로써의 coordinating controller에 대해서는 바로 아래에 이어질 "Compound 디자인 패턴으로써의 MVC"장에서 더 다루겠습니다

✔️ Medi. + Coord. Controller

NSObject를 상속받아 customize한 subclass의 인스턴스는 coordinating controller로써 완전히 적합할 수 있습니다
이런 류의 controller 객체는 mediating + coordinating 기능을 결합합니다

mediating 기능을 위해, view와 model간 data 이동 구현에 target-action/outlets/delegation 등과 같은 메커니즘을 이용합니다

이 인스턴스들은 많은 glue code를 포함하고 있는데 이 code는 독점적으로 app-specific하기 때문에, 가장 재사용성이 적은 종류의 객체입니다
*(glue code: 서로 호환성이 없는 code들을 connect하는 용도로만 사용되는 코드)


👨‍👩‍👦‍👦 MVC는 basic 디자인 패턴들이 합성된 것

Model-View-Controller는 몇가지 basic 디자인 패턴이 합성된 패턴입니다
이 basic 패턴들은 MVC app의 특징인 "기능 분리"와 "소통 경로"를 정의하기 위해 함께 동작합니다
하지만, 전통적인 MVC 개념에서는 Cocoa MVC와는 다른 basic 패턴들을 사용합니다
주된 차이점은 controller와 view에게 주는 역할 차이에 있습니다

✔️ 전통적인 MVC

원래(smalltalk시절) 컨셉에서는, MVC는 "Composite"+"Strategy"+"Observer" 패턴들로 구성되었습니다

  • Composite (view)
    view 객체들은 실제로 조직화된(coordinated) 방식으로 함께 동작하는 nested view의 복합체입니다 (즉, view hierarchy)
    이런 display 요소들은 window부터 compound view(ex. table view)나 individual view(ex. 버튼)까지 다양합니다
    유저 입력과 display는 any level의 compound 형태로든 만들어질 수 있습니다

  • Strategy (controller)
    controller 객체들은 하나 이상의 view객체들을 위한 전략?을 구현해야 합니다. view의 기능은 visual적인 측면을 유지하는 것에 한정되므로, 인터페이스 동작들이 구체적으로 어떻게 돌아갈지는 controller에게 위임합니다

  • Observer (model)
    model 객체는 model의 data가 궁금한 객체들(ex. view)에게 상태 변화를 계속해서 알려줍니다

Composite/Strategy/Observer 패턴이 함께 동작하는 전통적인 방식은 아래와 같습니다

유저는 view를 조작하고 결과적으로 event를 발생시킵니다
controller는 이 event를 받아 app-specific한 방식으로 해석(strategy)합니다
이 strategy는 model에게 상태변경을 요청하는 것일 수도 있고 view에게 외형이나 동작변경을 요청하는 것일 수도 있습니다
결국, model 객체는 상태가 바뀌면 observer로 등록된 모든 객체들에게 알립니다
만약, observer가 view라면 그에 따라 외형을 바꾸는 구현일 수도 있습니다

내피셜로 정리하면,
view의 조작이 model을 변경하려면 controller를 거쳐야 하지만,
model의 변경이 view에 적용되는건 model이 직접 합니다

✔️ Cocoa MVC

compound 패턴으로써의 Cocoa MVC는 전통적인 MVC와 일부 유사점이 있습니다

Cocoa에서 전통적인 MVC를 수정한 이론적인 이유
사실, 전통적인 MVC 구조에 기반한 app을 만들 수 있긴 합니다
bindings 같은 기술을 사용하면 model의 notification이 view로 다이렉트로 꽂히므로 Cocoa MVC app을 쉽게 만들 수 있습니다
하지만, view와 model 객체가 재사용성이 높도록 설계해야만 한다는 문제가 있습니다
(그래서 쓴다는거야 안 쓴다는거야..)

view라는 것은 app에 정의된 내용을 운영체제가 보고 느끼는대로 표현한 것입니다.
그러므로 운영체제 입장에서, 외형과 기능에 일관성이 필수적이고 객체들이 높은 수준의 재사용성을 가질 것을 요구합니다
(의미를 추측해보기론, view 객체들이 전부 다르고 재사용성이 떨어지면 화면에 띄워주는데 많은 load가 들어서인듯함. but... not sure)

model 객체는 정의에 의해 특정 영역에 연관된 data를 캡슐화하고 이 data들에 대한 동작을 수행합니다.

설계측면에서, 재사용성을 강화하기 위해 model과 view 객체를 서로 분리하는 것이 가장 좋습니다.

Cocoa MVC
대부분의 Cocoa app에서 model의 상태변화에 대한 notification들은 controller를 통해 view로 전달됩니다
(아래 그림 참고. 2개의 basic 패턴이 추가된 Cocoa MVC 디자인 패턴)

compound 디자인 패턴에서 controller는 "Strategy"에 더해 "Mediator" 패턴도 포함합니다.
mediator는 view와 model 사이의 쌍방향 data 흐름을 중재합니다
model의 상태변화는 controller를 통해 view로 전달됩니다
추가로, view는 target-action 메커니즘에 따라 "Command" 패턴을 추가로 포함합니다

Note: target-action 메커니즘
target-action은 view 객체가 유저의 입력과 선택을 전달할 수 있도록 하는 메커니즘입니다
coordinating/mediating controller 모두에서 구현될 수 있지만 메커니즘 설계는 서로 다릅니다

coordinating controller에서는 Interface Builder를 통해 view를 target(controller)에 연결하고 action을 결정합니다
coordinating controller는 "windows"와 app의 "global 객체"의 대표이므로 responder chain 안에 있을 수 있습니다?
(Coordinating controllers, by virtue of being delegates of windows and the global application object, can also be in the responder chain)

mediating controller에 사용되는 bindings 메커니즘도 역시 view를 target(controller)에 연결하고 action을 결정합니다
하지만, responder chain 안에 있지 않다는 차이가 있습니다

(Responder chain: Breadcrumbs님 포스팅)

전통적인 MVC를 수정한 실용적인 이유
전통적인 MVC를 수정한데에는 이론적인 이유뿐만 아니라 (특히, Mediator 디자인 패턴에 관해) 실용적인 이유들이 있습니다
mediating controller는 NSController의 concrete subclass들로부터 파생됩니다. 그리고 이 class들은 mediator 패턴을 구현하는 것 외에도 app이 사용해야 할 feature들을 제공해야 합니다 (ex. selection이나 placeholder 값을 관리 등)

그리고 만약 bindings 기술을 사용하지 않기로 결정했다면, model에게 notification을 주는 "Cocoa notification center"같은 메커니즘을 대안으로 사용할 수 있을지도 모릅니다
하지만 이렇게 하려면 custom view subclass를 만들어서 model에 의해 post되는 notification 정보를 추가해야 합니다

잘 설계된 Cocoa MVC app에서는, coordinating controller가 가끔 nib file에 저장된 mediating controller를 소유합니다 (아래 그림 참고)


🎯 MVC app 설계 가이드라인

다음 가이드라인들이 app을 설계할 때 MVC 고려사항에 적용됩니다

✔️ mediating controller를 만드는데 NSObject의 custom subclass를 이용할 수도 있지만... 굳이?

custom이 필요하다면 굳이 NSObject 대신 Cocoa bindings 기술이 이미 만들어져 있는 NSController의 custom subclass를 사용하면 됩니다
(NSOjbectController, NSArrayController 등)

하지만, app이 매우 심플하고 / outlets와 target-action을 사용하여 mediating 기능을 구현하기 위한 glue code를 쓰는게 편한 것 같으면, NSObject를 쓸 수도 있겠습니다
대신, NSObject의 custom subclass를 쓰려면 NSController에는 이미 구현되어 있는 것들을 추가로 구현해야 합니다 (key-value coding / KVO / editor protocols)

✔️ 하나의 객체에 MVC 역할들을 결합할 수 있더라도, 왠만하면 분리하는게 최선입니다

분리하는게 객체의 재사용성과 프로그램 확장성을 강화하기 때문입니다

만약 하나의 class에 MVC 역할들을 병합하고 싶다면, 일단 해당 class의 지배적인 역할을 하도록 구현하세요
그 후, 동일한 구현범주(ex.. 같은 파일?) 내에서 이를 확장하는 방식으로 사용하는게 유지보수에 좋습니다

✔️ MVC의 목적은 (최소한 이론적으로라도) 재사용성을 극대화하는 것입니다

특히, view와 model은 매우 재사용성이 높아야만 합니다 (앞서 언급했던 "이미 만들어져 있는" mediating controller들 또한 재사용가능합니다)

(아마 상대적으로 재사용성이 낮을 듯한?) app-specific 동작들은 최대한 controller 객체에 집중시키는 것이 일반적입니다

✔️ view가 model의 변화를 직접적으로 감지할 수도 있겠지만, 최선이 아니다

view는 model의 변화를 감지하기 위해 항상 mediating controller를 통해야 합니다. 그 이유는

  1. bindings 메커니즘을 사용하여 직접감시한다면, NSController와 그 subclass들이 제공하는 이점들을 취할 수 없습니다. (selection/placeholder 관리, 변화를 적용하고 폐기하는 능력)

  2. bindings 메커니즘을 없이 직접감시하려면, model에 의해 post되는 변화 notification 감지기능을 추가하기 위해 기존에 있던 view class를 상속한 subclass를 생성해야 합니다

✔️ class 간 code dependency를 최소화해야 합니다

class간 dependency가 커질수록, 재사용성은 줄어듭니다

이에 대한 구체적인 가이드는 연관된 두 class가 어떤 MVC 역할 조합인지에 따라 다른데,

  1. view는 model에 종속될 수 없습니다 (일부 custom view에서는 종속을 피할 수 없을지도 모르지만)

  2. view가 mediating controller에 종속될 필요는 없습니다 (선택적)

  3. model은 또 다른 model과 종속되면 안됩니다

  4. mediating controller는 model에 종속되면 안됩니다 (하지만 view와 마찬가지로, custom controller라면 종속을 피할 수 없을지도 모릅니다)

  5. coordinating controller는 app의 기능성을 감독/조정하는 역할로 app-specific하므로 자연히 모든 MVC 역할 타입들에 종속적입니다

✔️ Cocoa가 제공하는 아키텍쳐가 MVC 역할을 "구체적인 type에까지" 할당한다면, 사용하라

그리한다면, 프로젝트를 훨씬 쉽게 만들 수 있습니다

예로, Cocoa가 제공하는 document 아키텍쳐는 NSDocument 객체를 구성하는 Xcode 프로젝트 템플릿도 들어있습니다


😎 결론. Cocoa MVC

MVC 디자인 패턴은 많은 Cocoa 메커니즘/기술의 근간입니다.
결과적으로, 객체지향 디자인에서 MVC의 중요성은 단지 자신의 app을 위해 더 큰 재사용성과 확장성을 확보하는 것에 그치지 않습니다
만약 당신의 app에 MVC 기반 Cocoa 기술을 통합하려 할 때, app 설계도 MVC 패턴을 따른다면 가장 잘 동작할 수 있습니다
당신의 app이 MVC 역할을 잘 분리해놓았다면 이런 Cocoa 기술들을 상대적으로 고통없이 사용할 수 있습니다
하지만 그렇지않다면, 더 많은 노력이 들 것입니다

macOS에서의 Cocoa는 아래의 MVC기반 아키텍쳐/메커니즘/기술들을 포함합니다

✔️ Document 아키텍쳐

이 아키텍쳐에서는 document-based app이 크게 3가지 controller 객체로 구성됩니다

  1. NSDocumentController : app 전체를 위한 controller
  2. NSWindowController : 각 document window를 위한 controller
  3. NSDocument : 각 document에 대해 controller와 model역할이 합성된 객체

✔️ Bindings

MVC는 Cocoa의 bindings 기술의 핵심입니다. NSController라는 추상 class의 concrete subclass들은 이미 만들어진 controller 객체를 제공합니다
당신은 이 concrete subclass들의 객체를 view와 적절히 설계된 model 간 bindings를 만들기 위해 구성할 수 있습니다

✔️ Application scriptability (대본작성성?)

app이 scriptable하게 설계하려면, MVC 패턴을 따르는 것 뿐만 아니라 model을 적절하게 설계해야 합니다.
app의 상태에 접근하고 app에 어떤 행동을 요청하는 command를 scripting하는 것은 보통 model이나 controller로 보내져야 합니다

✔️ Core Data 프레임워크

Core Data 프레임워크는 model의 그래프를 관리하고 persistent store에 이들을 저장함으로써 model 객체의 지속성을 보장합니다

Core Data는 Cocoa bindings 기술과 긴밀하게 통합되어 있습니다
MVC와 객체 모델링 디자인 패턴들은 Core Data 아키텍쳐의 필수적인 결정 요소입니다

✔️ Undo 아키텍쳐

undo 아키텍쳐에서 model은 다시 한번 중추역할을 합니다.

model의 기본 메서드(보통 accessor 메서드같은)들은 보통 실행취소/재실행 기능을 구현하는 경우가 많습니다
또한, 어떤 action에 대한 view/controller 객체들도 실행취소/재실행 기능에 연관될지도 모릅니다

예로, 당신은 실행취소/재실행 메뉴 아이템에게 특정 title을 지정하는 객체를 가질지도 모릅니다
혹은, text view에서 어떤 selection을 취소하게 하는 객체를 가질지도 모릅니다

profile
노션으로 이사갑니다 https://tungsten-run-778.notion.site/Study-Archive-98e51c3793684d428070695d5722d1fe

1개의 댓글

comment-user-thumbnail
2021년 12월 8일

정리해주셔서 잘 봤습니다 감사합니다 :)

답글 달기