코코아 바인딩
기능적인 측면에서 가장 단순하게 설명하면, 코코아 바인딩은 모델과 뷰 사이에 어떠한 글루코드 없이 값과 디스플레이되는 것을 동기화시키는 것이다. 즉 바인딩을 구성하면 코드상에서 어떤 프로퍼티의 값이 변경될 때 별도의 업데이트 코드를 작성하지 않더라도 자동으로 뷰가 업데이트되고, 반대로 뷰에서 사용자가 값을 조작하는 이벤트가 발생하면 역시 아무런 코드가 없더라도 연결된 프로퍼티 값이 변경되게 하는 것이다.
코코아 바인딩은 코코아의 기본 기술들을 결합하여 구현되는데, 작성/유지해야 하는 코드의 양을 최소화하면서 뷰와 모델간의 바인딩을 거의 공짜로 제공한다. 클래스를 작성할 때, 키밸류 컨벤션만 따랐다면, 코코아 바인딩을 적용하기 위해서 기존 코드를 재작성할 필요도 없다.
모델-뷰-컨트롤러 패턴
코코아에서 가장 많이 쓰이는 패턴은 모델-뷰-컨트롤러이다. 코코아 앱은 모델, 뷰, 컨트롤러가 각각의 객체로 구성되고, 서로 다른 역할을 수행한다. (논란의 여지는 있지만) MVC에 일단 적응하고 나면, 많은 것들이 재사용 가능하고, 또 확장될 수 있다. 심지어 다른 애플리케이션에서도 모델 객체나 뷰 객체를 재사용할 수 있다.
대부분의 경우 컨트롤러가 하는 일은 일종의 ‘글루코드’를 담는 것인데, 이는 모델 데이터의 변경을 뷰에 반영하거나, 거꾸로 뷰에서 사용자가 만든 변경사항을 모델 데이터에 반영하는 것이다. 보통 이런 코드들은 앱별로 독특하게 돌아가는데다가 쓰기가 지루하다. 코코아 바인딩은 이런 코드의 양을 획기적으로 줄일 수 있게 한다.코코아 바인딩은 이런 글루코드의 대부분을 재사용 가능한 컨트롤러 객체로 대신한다.
예를 들어 슬라이더 하나와 텍스트 필드가 있는 창을 생각해보자. 슬라이더는 특정한 값 사이를 오갈 수 있고, 텍스트 필드는 그 값을 표현한다고 하자.
아래 그림은 이러한 관계를 target-action 방식으로 구현했을 때의 흐름을 나타내는데, 만약 뷰에서 슬라이더를 움직이면 컨트롤러는 -updateNumberFrom:
이라는 메시지를 받게 되고, 그로부터 슬라이더의 현재값을 읽어 모델이 되는 객체에 저장하고, 다시 텍스트 필드의 -setFloatValue:
를 호출하여 그 값을 반영하게 된다.
간단한 흐름이지만, 이를 실제로 구현하는 것은 제법 귀찮은 일이다. 일단 뷰컨트롤러에 -updateNumberFrom
메소드를 작성해야하고, 텍스트필드를 제어하기 위해 텍스트 필드에 대한 아웃렛을 명시하고 이를 인터페이스 빌더에서 연결하는 일도 해야한다.
코코아 바인딩은 이러한 컨트롤러 객체를 작성하는 대신, 미리 만들어진 컨트롤러 객체(NSObjectController
)를 이용해서, 두 객체의 값을 묶을 수 있다. 이를 통해서 두 UI 요소의 값은 항상 동기화된다. 이 과정에서 필요한 것은 모델 클래스가 적절한 프로퍼티를 갖도록 준비하고, 나머지는 모두 인터페이스 빌더에서 바인딩을 설정하기만 하면 된다.
또한 코코아 바인딩은 양방향으로 동작한다. 위의 흐름과 반대로 텍스트 필드에서 숫자를 변경했을 때 슬라이더를 업데이트하려면, 코코아 바인딩은 아무일도 하지 않아도 된다. 타깃-액션 방식으로 직접 업데이트하려면 반대방향에서의 흐름을 처리하기 위한 메소드와 아웃렛이 추가로 필요하다.
코코아 바인딩은 타깃-액션 패턴을 쓰지 않고 동작하기 때문에 마법처럼 보이기도 한다. 슬라이더는 컨트롤러에게 아무런 메시지를 보내지 않는다. 대신에 슬라이더가 움직이면 이는 컨트롤러에게 컨텐츠의 숫자값이 변경되었음을 알리고, 그 변경된 값이 무엇인지 알려준다. 그러면 컨트롤러는 모델의 숫자값을 변경하고 다시 텍스트 필드로 그 값을 업데이트한다.
바인딩 옵션
바인딩은 몇 가지 옵션을 제공하는데, 값 변환(value transform), 플레이스홀더, 다른 파라미터가 그것이다.
값 변환은 말 그대로 값에 대해 변환 필터를 적용할 수 있게 한다. Foundation은 NSValueTransformer
와 같은 변환 클래스와 미리 만들어진 몇 가지 서브 클래스를 제공해준다. 예를 들면 위 예제에서 슬라이더는 화씨 온도값을 사용하고, 텍스트 필드로는 섭씨 온도값을 출력한다고 할 때, 섭씨-화씨를 변환할 수 있는 변환기를 작성하여 추가하면 이 과정을 자동으로 처리해주게 된다.
MVC 패턴의 확장
코코아 바인딩 아키텍쳐는 종래의 커스텀 컨트롤러 하나가 UI를 관리하던 MVC를 확장한다. 코코아 바인딩은 NSController
를 상속받는 몇 가지 재사용 가능한 컨트롤러 클래스를 제공하고, 바인딩 기반의 앱에서 이러한 컨트롤러가 UI의 일부를 관리할 수 있다. 또 이러한 기본 컨트롤러 클래스는 서브클래싱이 가능하기에 NSArrayController를 상속받아 정렬이나 페칭 기능을 커스터마이징할 수도 있을 것이다.
보통의 컨셉에서 컨트롤러는 데이터 모델에 바로 묶이는 것 같지만 실질적으로 데이터 모델은 다른 컨트롤러에 의해 종속되어 있고, 컨트롤러는 그 컨트롤러나, 그 컨트롤러의 키패스를 통해서 간접적으로 바인딩되어 있다고 보는 편이 옳다.
지원 기술
코코아 바인딩은 KVC와 KVO에 거의 전적으로 의존하고 있다. 이 둘은 결합되어 키-밸류 바인딩을 만들어낸다.
NSController의 유용성
바인딩은 기본적으로 KVC, KVO를 지원하는 거의 모든 두 객체 사이에 구성될 수 있다. 뷰는 모델 객체와 직접 바인드 될 수 있지만 일반적인 바인딩 적용 케이스들은 개별 모델 객체들이나 모델 객체의 집합을 사용자 설정 시스템과 인터페이스하기 위해 컨트롤러 객체(NSController
, MVC의 컨트롤러하고는 약간 다른 이야기다)를 사용한다.
NSController
객체는 자신의 현재 선택값과 플레이스 홀더 값을 관리한다. 이런 추가적인 기능을 통해 현재의 선택값이 없거나 복수 선택값을 가진 경우등 표현이 애매한 경우라도 적절한 값을 표시하게 할 수 있다.NSController
는NSEditor
,NSEditorRegistration
프로토콜을 지원한다.NSEditorRegistration
프로토콜은 에디터(뷰)에게 처리되지 않은 변경 사항이 있음을 알려줄 수 있다.NSEditor
프로토콜은 이 변경사항을 무시하거나 저장할 수 있게 한다. 예를 들어 사용자가 텍스트 필드에 타이핑을 하고 다른 버튼을 누르면 컨트롤러는 버튼 액션이 일어나기 전에 입력값이 완료된 것인지를 확인받는 절차를 거치게 할 수 있다. 이러한 메소드들은 주로 컨트롤러에 의해서 다른 UI요소들로 보내지지만, 반대로 컨트롤러가 이를 받을 수도 있다. (저장을 시도하거나 종료를 시도할 때 확인 받기)
NSController 클래스
NSController
는 추상클래스로 이 자식 클래스는 NSObjectController
, NSUserDefaultController
, NSArrayController
, NSTreeController
등이 있다.
NSObjectController
는 단일 객체의 프로퍼티들을 관리하며 위에서 언급된 기능들을 제공한다. NSUserDefaultController
는 사용자 설정 저장 시스템과의 인터페이스를 제공한다.
NSArrayController
와 NSTreeController
는 모델 객체의 집합을 관리하며 현재 선택된 값을 추적한다. 이러한 집합 컨트롤러들은 새로운 객체를 추가하거나, 선택된 객체를 제거할 수도 있다. 집합 컨트롤러가 관리하는 객체들은 컨테이너가 인덱스 방식 액세스만 지원한다면(-objectAtIndex
, -objectForKey
) 처리가 가능하다.
어디에 적용될 수 있을까
거의 대부분의 앱킷 객체는 바인딩을 적용할 수 있다.
실제 예시
게임앱을 예로 들어보자. 사용자는 일련의 전사를 관리하는데, 각각의 전사는 3개씩 무기를 가지고 다닐 수 있으며, 이 중 어느 하나를 UI를 통해 선택할 수 있다고 하자. 앱에서는 창 상단에 전사의 목록을 보여주고, 하단에는 그 전사가 선택한 무기를 보여줄 수 있다. 이는 아래 그림과 같은 UI로 표현된다고 가정한다.
전사는 Combatant
클래스의 객체로 표현되는데, 이 클래스에서 각각의 무기는 개별 인스턴스 변수로 정의되며, 각 변수값은 ‘인덱스 기반’으로 액세스가 가능하다고 가정한다.(쉽게 말해 배열이다.) 따라서 전사 클래스는 배열 컨트롤러를 통해서 무기에 액세스할 수 있다.
전사와 관련된 바인딩의 구성 요소는 다음과 같다.
- 창의 제목 : 전사 이름을 선택하면 창의 제목은 전사의 이름으로 변경된다.
- 무기 목록 : 전사가 변경되면 하단의 선택된 무기 값도 해당 전사의 것으로 변경된다.
- 무기 목록의 선택지: 무기 목록 중에서 선택된 무기는 해당 전사가 현재 들고 있는 무기로 표시된다.
바인딩 구성도는 다음과 같다.
- 두 개의 배열 컨트롤러가 필요하다. 전사의 배열과, 그 중 선택된 전사의 무기에 대한 배열이 있고 이들이 바인딩에 관련된다.
- 전사의 배열 전체를
contentArray
로 갖는 컨트롤러를 하나 생성한다. 이를 배열A라 하자. (contentArray 키를 뷰컨트롤러에 바인딩하고, 키패스를 warriers등 해당 프로퍼티 이름으로 준다.) - 다른 하나의 배열 컨트롤러(배열B)는 배열A에서 선택된 전사의 무기 목록을 표시한다. 따라서 이 배열B의
contentArray
는 다른 배열A의selection
에 해당하는 객체의weapons
프로퍼티에 바인딩되어야 한다. 따라서 배열 B의contentArray
키의 바인딩 대상은 ,배열A, 연결될 키패스는"selection.weapons"
가 된다. - 창의 타이틀은 배열A의 선택된 전사 이름이다. 타이틀 속성은 배열A에 바인딩하며, 이 때의 키는
"selection.name"
이 된다. - 전사 선택 박스의
value
값은 A의 전사의 이름 목록이다. 배열A와 바인딩하며 키이름은arrangedObjects.name
이 된다. - 이제, 전사 목록에서 하나를 선택하면 배열A의
selection
이 해당 전사 객체를 가리키는 프록시가 된다. - 무기 목록은 선택된 전사의 것을 가져올 것것이다. 해당 요소의 value 속성은 배열B에 대해 arrangedObjects 키에 바인딩된다.
- 무기 목록의 선택값은 다시 배열A에 바인딩하며 연결할 키패스는
"selection.selectedWeapon"
키패스로 연결된다.
이 구성은 다음과 같은 점을 시사한다.
- 하나의 앱에서 하나 이상의 오브젝트 컨트롤러가 사용될 수 있다.
- 하나의 UI 요소에 대해서도 다른 요소값들이 별개의 컨트롤러와 바인딩될 수 있다.
- 커스텀 모델 객체를 쓸 수 있다.