The Core Application Architectures
OSXDEV
사용자가 Cocoa 응용프로그램을 동작시키면 객체의 네트웍이 위치하게 됩니다. 이 런타임 네트웍은 각자 특정 역할을 수행하는 다양한 Application Kit 객체들로 구성되어 있습니다. 이들 객체는 소유, 의존, 협력에 의해 정의된 다양한 방식으로 서로 연관되어 있습니다. 이 챕터는 이 응용프로그램 아키텍쳐를 살펴보며 핵심 객체들의 역할, 주 애트리뷰트, 그들 간의 관계에 대해 살펴봅니다.
목차
|
[편집] The Event-Drawing Cycle, Revisited
객체 네트웍의 전반적인 목적은 이벤트와 드로잉 사이클 작업을 더 빠르게 하는데 있습니다. ("Adding Behavior to a Cocoa Program"에서 봤던) Figure 6-1에서 이 사이클을 보여줍니다.
Figure 6-1 The event and drawing cycle

여기서는 그래픽 유저 인터페이스를 보여주고 있습니다. 사용자는 마우스나 키보드를 사용하여 인터페이스와 상호작용하며 데이터를 입력하거나 선택을 지정합니다. 그리고 이 입력은 이벤트로 전환되어 애플리케이션으로 전해지며 이벤트 큐에 위치하게 됩니다. 응용프로그램은 큐의 각 이벤트를 위해 그 이벤트를 처리하기에 가장 적합한 객체나 객체들을 찾아주고 그후 그 이벤트가 처리된 후 응용프로그램은 사용자에게 디스플레이하는 것을 적합하게 수정합니다. 그 후, 응용프로그램은 큐에 있는 다음 이벤트를 처리하고 이 사이클이 다시 시작됩니다.
이 아키텍처에 참여하는 핵심 객체는 NSApplication, NSWindow, NSView 객체의 직간접적인 후손들입니다.
[편집] The Global Application Object
모든 Cocoa 응용프로그램은 글로벌 변수인 NSApp 이라는 이름으로 알려진 하나의 NSApplication 객체에 의해 처리됩니다. 이 NSApplication의 싱글턴 인스턴스(혹은 커스텀 서브클래스의 인스턴스)는 중요한 책임을 집니다. 응용프로그램을 타겟으로 하는 사용자와 시스템 이벤트를 획득하는 것과 그들을 적절한 객체에 디스패칭하는 것입니다. 또한, 응용프로그램의 창이 키인지 메인인지 여부를 트래킹 하는 것을 포함해 창을 관리할 책임도 있습니다. ("Window Status"를 보십시오.)
[편집] The Main Event Loop
NSApp이 하는 대다수의 일은 응용프로그램의 메인 이벤트 루프에서 일어나며 그것은 이벤트와 드로잉 사이클의 기반입니다. 메인 이벤트 루프가 작동하는지 이해하려면 Cocoa 응용프로그램의 main 엔트리 포인트에서 무엇이 일어나는지를 아는 것이 도움이 됩니다. 표준 Xcode Cocoa 프로젝트에서 main은 NSApplicationMain 단일 함수 호출을 갖고 있습니다. 이 함수는 세가지 중요한 일을 다음의 순서로 작업합니다.
1. sharedApplication 클래스 메소드를 호출하여 공유된 애플리케이션 객체(NSApp)를 획득합니다.
2. 애플리케이션의 메인 nib 파일을 메모리에 로드합니다.
3. 애플리케이션을 동작시킵니다. ([NSApp run])
몇몇 백그라운드 정보로부터 시작하여, 이들 스텝을 좀더 상세히 살펴봅시다. 동작하는 애플리케이션은 기본적으로는 프로세스이며 각 프로세스는 메인 쓰레드와 하나 혹은 그 이상의 부차적인 쓰레드가 있을 수 있습니다. 각 쓰레드는 언제나 런룹을 가지고 있습니다. 런룹은 프로세스로의 입력 소스를 모니터하고 소스가 프로세싱할 수 있는 상태가 되면 그 컨트롤을 디스패치하는 기법입니다.
싱글턴 인스턴스를 확보하는 것 외에 sharedApplication 메소드가 하는 주 작업은 윈도 서버로부터 이벤트를 받고 처리하기 위한 프로그래밍 인프라스트럭쳐를 설정하는 것입니다. 글로벌 애플리케이션 객체를 초기화하는데 있어서 NSApplication은 이벤트를 받기 위한(Mach 포트로 구현된) 이벤트 소스를 생성하는 것으로 윈도 서버와의 연결을 설정합니다. 또한 응용프로그램의 이벤트 큐를 이벤트가 도달하면 이벤트 소스로부터 이벤트를 큐에 더하는 FIFO 기법으로 설정합니다. 마지막으로, NSApplication은 메인 런룹(메인쓰레드의 런룹)을 입력 소스를 이벤트 소스로 갖도록 초기화합니다.
Figure 6-2 Main event loop, with event source

윈도 서버는 I/O Kit 장치 드라이버로부터 이벤트를 받고, 적절한 프로세스에 이벤트를 디스패치합니다. 프로세스는 런룹의 이벤트 소스로부터 이벤트를 받아 큐에 놓습니다.
응용프로그램이 메인 nib 파일을 로드할 때, 파일안의 객체는 객체사이의 연결과 함께 언아카이브됩니다. 메인 nib 파일은 언제나 응용프로그램의 메뉴를 포함하고 있으며 하나 혹은 그 이상의 창 객체를 (그들을 구성하는 뷰와 함께) 포함하고 있을 수 있습니다. 다른 nib 파일도 프로그램 시작시에 언아카이브 될 수 있습니다. 예를 들어, 문서 기반 응용프로그램은 사용자가 파일을 Finder에서 더블클릭했을 때, 문서의 nib파일을 언아카이브 할 수 있습니다. 이 초기 그래픽 유저 인터페이스는 사용자들이 요청을 하는데 (예를 들어 메뉴 아이템을 선택하는 것을 통해) 필요하며, 결과적으로 응용프로그램이 처리해야할 이벤트를 생성하게 됩니다.
NSApplication의 run 메소드는 메인 이벤트 루프의 일꾼입니다. 먼저 해당 응용프로그램을 위한 Apple 이벤트 핸들러를 등록합니다. ("Handling Apple Events"를 보십시오) 그후 닫힌 while 루프에서 run은 응용프로그램이 종료될 때까지 다음의 일들을 수행합니다.
1. 런룹에서 대기중인 윈도-디스플레이 옵저버를 서비스합니다. (Figure 6-3을 보면 "더럽다(dirty)"라고 표시된 윈도우 내의 범위를 다시 그리게 합니다.)
Figure 6-3 Main event loop, with run-loop observer

2. 이벤트 큐에서 다음 이벤트를 받습니다. (nextEventMatchingMask:untilDate:inMode:dequeue:)
3. 이벤트를 다음에 처리할 객체에게 디스패치합니다. 보통 이 객체는 NSWindow 객체입니다.(sendEvent:) “More About Event Dispatch” 에서 더 명확히 알 수 있습니다. 궁극적으로 다수의 객체가 이벤트를 처리하는데 관여하게 될 수 있으며, 콜 스택도 상당히 커질 수 있습니다. 이벤트가 처리되고 난 후에, 컨트롤은 run으로 돌아옵니다.
메인 이벤트 루프의 유입 지점은 nextEventMatchingMask:untilDate:inMode:dequeue: 메소드입니다. 이벤트 큐에 이벤트가 있다면 이 메소드가 큐에 가장 상위에 있는 이벤트를 가져와서 구조체를 NSEvent 객체로 변환합니다. 만일 큐에 이벤트가 없다면 메소드는 블락합니다. 메소드 블락 기간 동안 윈도우 서버로 부터 오는 새로운 이벤트가 처리되어 큐에 위치합니다. 새로운 이벤트의 존재가 nextEventMatchingMask:untilDate:inMode:dequeue: 메소드를 깨우고, 큐에서 처음 매칭하는 이벤트를 반환합니다.
만일 창의 오토-디스플레이 기능이 활성화되어 있고 디스플레이 하도록 표시된 뷰를 가지고 있다면, 그 런 룹에 옵저버(입력 소스)를 설치합니다. NSApp이 큐의 다음 이벤트를 처리하기 전에 이 옵저버가 작동하여 이들 뷰를 다시 그리도록 합니다. 창의 컨텐츠의 자동 디스플레이에 대해 더 알고 싶다면, "Windows and Drawing"을 보십시오.
[편집] More About Event Dispatch
NSApp의 sendEvent: 구현에서, 전달된 이벤트의 타입을 살펴보고 그에 맞춰서 디스패치합니다. 주로 디스패치의 타겟은 응용프로그램의 창이며, 응용프로그램은 해당 NSWindow 객체의 sendEvent: 객체를 호출하는 것으로 이벤트를 전달합니다. 키보드와 마우스, 이 두 주요 입력 이벤트의 타입을 위해 NSApp 은 적절한 창을 찾기 위해 다른 접근방법을 사용합니다.
사용자가 키보드에서 키를 누르면 키 이벤트가 생성됩니다. NSApp은 이들 이벤트를 키 윈도(현재 키 입력을 받는 응용프로그램의 창)에 전달합니다. (단축키와 같은 경우 응용프로그램은 디스패치 하는 것 대신 NSKeyDown 이벤트를 처리합니다.)
사용자가 그림 프로그램에서 그래픽 형태와 같은 창의 객체를 마우스로 클릭하면, 마우스 이벤트가 생성됩니다. NSApp은 마우스 이벤트가 발생한 창에 마우스 이벤트를 디스패치합니다.
만일 클릭되거나 뭔가 가해진 객체가 버튼이나 슬라이더와 같이 컨트롤 객체라면 그 객체는 응용프로그램에 다른 종류의 메시지, 바로 액션 메시지를 보냅니다. 이 메시지는 NSApplication의 sendAction:to:from: 메소드를 호출합니다. 만일 이 메시지의 타겟이 지정되지 않는다면 이 메소드는 응용프로그램의 키 (필요하다면 메인) 윈도의 리스폰더 체인에서 적절한 타겟을 찾습니다. 그후 그 타겟에 액션 메시지를 전달합니다.
NSApp은 sendEvent: 에서 다른 종류의 이벤트를 다룹니다. 몇몇 이벤트는 (활성화와 비활성와와 같이) 응용프로그램 자체와 관련되어 있을 수 있고, NSApp은 이들을 직접 다룹니다. 다른 이벤트들은 하나 혹은 그 이상의 창과 관련되어 있습니다. 예를 들어, 창이 노출되거나 스크린 해상도에 변화가 발생했을 수 있습니다. 이런 경우 NSApp은 적절한 메소드를 영향을 받은 창에서 호출합니다.
[편집] Window Management
글로벌 애플리케이션 객체의 일중의 하나는 응용프로그램의 창들을 관리하는 것 입니다. NSApp은 다음 창 관리 작업들을 수행합니다.
- 현재 키 윈도와 메인 윈도를 추적합니다.
- 창을 숨기거나 나타냅니다.
- 응용프로그램 종료시 창들을 할당해제(deallocate)합니다.
- 응용프로그램 스위칭시, 창 활성화/비활성화를 관리합니다.
“More About Event Dispatch” 에 언급된 것처럼, NSApp은 창에 특정한 이벤트와 액션 메시지를 해당 창에 디스패치합니다. 또한, 응용프로그램의 Window 메뉴를 유지하며 모달 윈도와 패널을 관리합니다. (이에 대한 설명은 "Modal Windows"에 되어 있습니다.)
창의 위치를 알기 위해, 애플리케이션 객체는 윈도 리스트라고 불리는 레퍼런스를 가지고 있습니다. (Figure 6-4를 보십시오.) (할당 해제와 같은) 몇몇 윈도 관리 작업을 위해 이 목록을 순환합니다. (NSApp에 windows 메소드를 보내 언제든지 창 목록을 얻을 수 있습니다.) 애플리케이션 객체는 또한 windowWithWindowNumber:에서 현재 NSEvent 객체로부터 얻은 윈도 번호를 사용하여 윈도를 찾을 수도 있습니다.
Figure 6-4 The application's window list

[편집] Handling Apple Events
응용프로그램이 처리하는 모든 이벤트가 이벤트 큐로부터 오는 것은 아닙니다. Finder나 Launch Services와 같은 Mac OS X 시스템의 다른 프로세스도 애플 이벤트를 사용해 다른 프로세스와 통신합니다. 그들은 종종 응용프로그램에게 애플 이벤트를 보내어 사용자가 Finder 창에서 문서를 더블클릭 했다거나 애플 메뉴에서 종료를 선택해서 응용프로그램이 종료하도록 요청한다거나 하는 등의 통지를 보냅니다.
Cocoa 응용프로그램이 시작될 때, 가장 먼저 하는 일 중의 하나는 몇몇 애플 이벤트 핸들러를 등록하는 것입니다. 애플 이벤트가 응용 프로그램에 전송될 때, 적절한 핸들러가 처리되는 것입니다. Cocoa 응용프로그램은 다음 애플 이벤트를 위한 핸들러를 등록합니다.
| Apple ID | Description |
|---|---|
| kAEOpenApplication | 응용프로그램을 구동합니다. |
| kAEReopenApplication | 응용프로그램을 다시 오픈합니다. 사용자가 독에서 응용프로그램의 아이콘을 클릭하는 경우 같을 때 보내집니다. |
| kAEOpenDocuments | 응용프로그램에게 열어야할 문서 목록을 제공합니다. 보통 사용자가 Finder에서 하나 혹은 그 이상의 문서를 더블 클릭했을 때 보내집니다. |
| kAEPrintDocuments | 응용프로그램에 인쇄할 문서의 목록을 제공합니다. 보통 사용자가 Finder에서 하나 혹은 그 이상의 문서를 선택하고 File 메뉴에서 Print를 선택하면 보내집니다. |
| kAEOpenContents | 텍스트와 이미지 같이 드래그된 컨텐츠를 응용프로그램에 제공합니다. 이 이벤트는 보통 사용자가 파일을 독의 응용프로그램 아이콘에 드래그 했을때 보내집니다. |
| kAEQuitApplication | 응용프로그램을 종료하도록 요청합니다. |
[편집] Windows
응용프로그램은 윈도를 사용하여 확보한 스크린 영역에 컨텐트와 사용자 액션에 대한 응답을 표시합니다. 윈도는 드로잉과 이벤트 처리 모두에 필수적입니다.
[편집] The Windows of an Application
(백그라운드 응용프로그램과 같이) 보이는 창이 없는 응용프로그램이 있을 수 있으나, 이런 프로그램은 흔치 않습니다. 일반적으로 응용프로그램이 가질 수 있는 창의 수에 따라 두종류로 구별할 수 있습니다.
- 문서-기반 : 다수의 문서를 각각의 창에 생성할 수 있는 능력이 있는 응용프로그램입니다. 워드 프로세싱이나 드로잉 프로그램을 예로 들 수 있습니다. 문서 기반 응용프로그램에서, 사용자는 메뉴 옵션(주로 File > New)을 선택하여 새 문서를 생성합니다. 코코아에서 대부분의 문서 기반 응용프로그램은 다큐먼트 아키텍쳐에 의존하고 있습니다. (이 아키텍쳐에 대한 개요는 "Other Cocoa Architectures"에서 찾을 수 있습니다.)
- 단일-문서 : 한번에 하나의 창만 표시할 수 있는 응용프로그램입니다. Mac OS X 의 예로는 iSync와 Font Book을 들 수 있습니다. 단일 문서 응용프로그램이 구동되면 창을 디스플레이합니다. 보통 창을 닫으면 응용프로그램이 종료됩니다.
어느 응용프로그램이든 다이얼로그나 패널로 알려진 부차적인 창을 가질 수 있습니다. 이들 창은 현재 문서 창에 종속되어 있거나, 단일-윈도 응용프로그램의 경우 메인 윈도에 종속되어 있습니다. 이들은 문서나 메인 창을 다양한 방식으로 보조합니다. 예를 들어, 폰트나 색상의 선택, 팔렛트의 도구 선택, 경고창 디스플레이등을 들 수 있습니다. 부차적인 창은 보통 모달입니다. 더 많은 정보는 "Panels"에서 찾을 수 있습니다.
[편집] NSWindow and the Window Server
Cocoa의 NSWindow 객체는 물리적인 창을 표시합니다. 윈도 서버는 물리적인 창을 생성하고 궁극적으로는 스크린상에서 그들을 다룹니다. 각각의 윈도에 독특한 번호를 부여하여 구분자로 사용합니다. NSWindow 객체와 물리적인 창 사이의 연결은 이 윈도 번호를 통해 설정됩니다.
윈도 서버가 창을 생성할 때, 윈도 그래픽 컨텍스트를 획득하고 그래픽-스테이트 스택을 초기화합니다. 또한 윈도의 배킹 스토어(backing store)를 생성하여 윈도의 픽셀 값을 담고 있는 메모리 범위를 디스플레이 장치의 프레임 버퍼에 위치하도록 합니다.
[편집] Window Buffering
스크린에 하나 이상의 창이 있으면 종종 오버랩됩니다. 윈도의 그룹이 서로 겹치게 되면 일부가 숨겨진 창을 클릭하면 주로 그 창을 앞으로 가져오게 합니다. 이 과정에서 숨겨졌던 내용물이 보여지게 됩니다. 윈도가 디스플레이 버퍼를 가지고 있다면, 윈도 서버는 자동으로 컨텐츠들을 보이게 합니다. 그렇지 않다면, 응용프로그램이 직접 그려줘야 합니다.
윈도 버퍼링은 두 종류가 있습니다. 윈도의 디스플레이 버퍼는 전체 윈도우나 현재 가려진 윈도의 일부를 위한 픽셀 값을 저장합니다. 디스플레이 버퍼를 갖고있는 숨겨졌던 창이 맨 앞으로 오게 되면 윈도 서버는 디스플레이 버퍼로부터 노출된 영역을 스크린으로 복사합니다. NSWindow 객체를 생성할 때, Figure 6-5에 묘사된 세가지 버퍼링 스키마 중 하나를 지정할 수 있습니다.
- Buffered 버퍼가 배킹 스토어에 있는 것의 정확한 사본을 유지합니다. 사실, 드로잉은 디스플레이 버퍼에서 일어나고 컨텐츠가 스크린으로 플러쉬되고, 그림자나 투명도가 관련되어 있으면 겹치는 창과 통합됩니다. 만일 창이 가려진 후 앞으로 오게 되면 전체 디스플레이 버퍼는 스크린으로 복사됩니다.
- Retained 리테인된 창이 디스플레이를 갖고 있더라도 드로잉은 창이 드러나고 알파 컴포넌트가 그려지지 않는 경우에만 스크린에 직접 수행됩니다. 창의 일부가 가려지면, 표시된 컨텐츠는 버퍼로 복사되고, 버퍼에 드로잉이 수행됩니다. 버퍼에 수정된 부분은 디스플레이로 복사됩니다. 디스플레이에서 창의 가려진 부분이 후에 보여지게 되면, 디스플레이 버퍼의 컨텐츠가 스크린으로 복사됩니다.
- Nonretained 디스플레이 버퍼가 제공되지 않습니다. 모든 드로잉은 스크린에 직접 발생합니다. 그것은 픽셀 값이 프레임 버퍼에 직접 입력됩니다. 만일 창의 일부가 다른 창에 의해 가려지면, 창의 컨텐츠 영역의 비트들은 잃어버리게 됩니다. 가려진 창이 다시 드러나게 되면, 응용프로그램은 가려졌던 부분을 반드시 다시 그려야 합니다. 그리지 않는다면, 창의 배경 색으로 표시되게 됩니다.
Figure 6-5 Window buffering schemes

버퍼된(buffered) 창만이 드로잉에서의 알파 채널을 사용하는 것을 포함하는 투명효과를 지원합니다. Quartz 드로잉 시스템이 드로잉에서 알파 사용에 상당히 많이 의존하기 때문에, Quartz와 함께 사용는 창은 반드시 버퍼된 창이어야 하며 Application Kit과 같이 Quartz에 의존하는 컴포넌트도 언제나 버퍼된 창을 사용해야 합니다. 리테인하지 않는 창은 임시 이미지나 타겟-액션 커넥션을 위해 Interface Builder에서 사용하는 것 같은 단순한 연결 라인에 사용할 수 있습니다. 그러나, 리테인하지 않은 창과 리테인된 창의 사용은 보통 추천하지 않습니다. 그 이유는 이런 창이 있으면 현대 유저 인터페이스와 잘 공존하지 않으며 성능 저하를 가져올 수 있기 때문입니다.
[편집] Window Z-Order and Levels
윈도 서버는 스크린 리스트 혹은 Z-order 라고 알려져 있는 앞에서 뒤 순서로 스크린에 디스플레이하는 창을 관리합니다. 윈도는 이 리스트의 맨 위에 있어서 최상위 창이 될 수도 있습니다. 혹은, 리스트의 맨 아래에 있어서 다른 모든 창에 의해 겹쳐져 있게될 수도 있습니다. 혹은, 창들 사이에 어디든 위치하게 될 수도 있습니다. Z-order는 사용자가 창을 클릭할 때마다 변하게 되며 클릭한 창을 최상위 창이 되게 하며 이전 최상위 창은 그 아래로 위치하게 됩니다.
윈도 레이어링의 컨셉은 윈도 레벨에 의해 복잡해집니다. 레벨은 특정 기능 타입의 윈도의 계층화된 서브셋 입니다. 윈도 레벨은 계층적 순서로 되어 있으며 높은 레벨에 있는 윈도는 낮은 레벨에 있는 윈도 위에 디스플레이됩니다. 이 스키마는 특정 타입의 창이 언제나 다른 수준의 창의 위나 아래에 위치하도록 해줍니다. 예를 들어, 모달 시스템 다이얼로그는 언제나 모든 응용프로그램 창 위에 뜨게 됩니다. 따라서 Z-order는 윈도 레벨 내에서 유지됩니다. (Figure 6-6을 보십시오.)
Figure 6-6 Window levels and Z-order

윈도 서버는 몇몇 윈도 레벨을 다음 순서로 지원합니다.
1. 스크린 세이버. (스크린 세이버는 스크린 크기의 창을 컨텐츠를 표시할 타이틀 바 없이 사용합니다.) 스크린-세이버 창은 다른 모든 창 위에 디스플레이 됩니다. 2. 응용프로그램 메뉴를 포함한 메뉴 바 3. 독(Dock) 4. 모달 창과 패널 ("Modal Windows"를 보십시오) 5. 컨텍스츄얼 메뉴 6. 플로팅 윈도(예. 그림 프로그램에서 팔렛트 창) 7. 응용프로그램 윈도를 포함한 다른 모든 종류의 창
윈도가 오프스크린 윈도로 알려지게 되면 스크린 리스트에서 명시적으로 제거될 수 있습니다. 창은 리스트로부터 제거되면 사라지게되고 리스트에 다시 돌아오면 스크린에 다시 표시됩니다. 오프스크린 윈도에는 이벤트가 디스패치되지 않습니다. 창을 스크린 리스트에서 제거하는 것은 윈도-숨기기 기능의 기반입니다. 오프스크린 윈도는 버퍼되거나 리테인 되어야 그려질 수 있습니다.
NSWindow는 응용프로그램 윈도의 Z-order를 다루는 메소드를 정의합니다. 스크린 리스트로부터 창을 제거하거나 추가하고, 윈도 레벨을 설정합니다. 상세한 정보는 NSWindow 클래스 레퍼런스에서 찾을 수 있습니다.
[편집] Parts of a Window
창은 Figure 6-7에 보여지는 것처럼 프레임 영역과 컨텐트 영역 두가지 주요 부분이 있습니다. 이들 영역은 특히 NSView의 서브클래스의 인스턴스인 뷰들입니다. ("Views" 를 보십시오.) 프레임 뷰는 전체 창 영역을 감싸고 창의 경계와 타이틀 바를 그립니다. 프레임 뷰는 NSWindow에 의해 생성된 프라이빗 객체입니다. 서브클래싱하여 수정하도록 되어 있지 않습니다. 그러나, 창을 생성할 때, 프레임 뷰가 어느 컨트롤과 기능들을(닫기 버튼, 축소 버튼, 크기 변환 삼각형, 타이틀) 갖도록 할지는 지정해 줄 수 있습니다.
Figure 6-7 A window's frame view and content view

컨텐트 뷰는 프레임 뷰에 삽입되어 보통 타이틀 바와 창 경계를 제외한 전체 영역을 차지합니다. Figure 6-7은 컨텐트 뷰를 프레임 뷰에 비교하여 보여줍니다. (타이틀 바나 보이는 경계가 없는 창을 만들 수 있지만, 여전히 프레임 뷰는 존재합니다.) 컨텐트 뷰는 프레임 뷰의 유일한 퍼블릭 서브뷰 입니다. 프라이빗 뷰가 아니기 때문에 원한다면 커스텀 뷰로 대치할 수 있습니다. 비록 컨텐트 뷰가 (뷰 계층에서 서브뷰를 소유하고 포함하는 뷰인) 슈퍼뷰를 갖지만 이 슈퍼뷰는 프라이빗 객체입니다. 따라서, 컨텐트 뷰는 창의 뷰 계층의 루트라고 할 수 있습니다. 뷰 계층에 대해 더 많은 정보는 "Views"에서 얻을 수 있습니다.
모든 뷰 객체가 그렇듯이, 컨텐트 뷰는 윈도의 레퍼런스를 가지고 있고, window 메소드로 접근할 수 있습니다. Figure 6-8은 NSApp과 그 윈도 리스트, 리스트 내의 각 창의 컨텐트 뷰, 그리고 이들 객체 사이의 관계를 보여줍니다.
Figure 6-8 Relationships among NSApp, windows, and content views

[편집] Window Coordinates
창의 좌표는 스크린의 좌표와 관련되어 있습니다. 전체 스크린은 좌측 하단이 원점이고 수평으로는 x축이 증가하며 수직으로는 y축이 증가하는 2차원 좌표 그리드의 1사분면이라고 생각할 수 있습니다. (Figure 6-9를 보십시오) 이 그리드를 대조하여 스크린에서 포인트를 위치시킬 수 있습니다.
Figure 6-9 Screen coordinates

화면 좌표계의 기본적인 기능은 화면상에서 창을 위치시키는 것입니다. 응용프로그램이 새 창을 만들어 화면에 놓으면, 창의 초기 크기와 화면 좌표에서의 위치를 지정해야만 합니다. 그러나 창을 그리기 위해서는 창에 특정한 다른 좌표 시스템인 기본 좌표계를 사용합니다. (Figure 6-10을 보십시오) 이 좌표계는 화면 좌표계와 두가지 측면에서 다릅니다.
- 특정 창에만 적용됩니다. 각각의 창은 자신만의 기본 좌표계를 갖습니다.
- 화면의 좌하단이 아니라 창의 좌 하단이 원점이 됩니다. 창이 움직이면, 원점과 전체 좌표계가 함께 움직입니다. 이미지는 창이 어디에 위치하든 상관없이 자신의 위치를 기본 좌표계에 유지시킵니다.
Figure 6-10 Base coordinate system

기본 좌표계는 창의 뷰의 각각의 좌표 시스템을 정의하기 위한 레퍼런스 포인트입니다. 프레임 뷰는 창의 경계와 타이틀 바를 기본 좌표계에 직접 그립니다. 컨텐트 뷰와 그 서브뷰들은 기본 좌표계의 변환인(transformation) 좌표 시스템에 그립니다.
[편집] Windows and Drawing
창은 자기자신을 그리지 않습니다. 이 역할은 그들이 포함하는 뷰에 달려 있습니다. 그러나 NSWindow 객체는 속한 뷰의 드로잉을 조절하는데 중요한 역할을 합니다.
일반적으로 이벤트 루프가 도는 동안 응용프로그램의 객체들은 뷰(혹은 뷰의 일부)가 다시 그려질 필요가 있다고 표시할 수 있습니다. NSWindow 객체는 뷰 계층의 위치로 결정된 순서인 리스트 내의 이런 "더러운(dirty)" 뷰의 레퍼런스를 수집합니다. 잠시 후 (보통 이벤트 사이클의 끝에서) Application Kit은 이 목록을 최상위 뷰(content 뷰에서 가장 가까운 뷰)부터 순서대로 자신을 그리도록 요청합니다. 이런 방식으로 다른 뷰들의 뒤에 있는 뷰가 먼저 그려집니다.
이런 창의 뷰의 자동 드로잉은 창의 기본적으로 설정되어 있는 자동-디스플레이 기능이 켜져 있을 때에만 발생합니다. (setAutodisplay: 메소드를 보십시오) 만일 이 기능을 끈다면, 응용프로그램은 필요할 때, 윈도의 컨텐트를 업데이트 시킬 책임을 져야 합니다. 추가로, display, displayRect: 혹은 displayRectIgnoringOpacity: 메시지를 NSView 객체 에 보내어 자동-디스플레이 기법을 우회할 수 있습니다. 그 결과로 그 뷰와 서브뷰는 즉시 다시 그려지게 됩니다. "Displaying a View"에서 추가 정보를 볼 수 있습니다.
NSWindow의 display와 displayIfNeeded 메소드를 사용하여 전체 창을 다시 그릴 수 있습니다. 후자의 메소드는 무효화된(invalidated) 뷰의 리스트만 돌지만, 이들 메소드는 창의 뷰를 즉시 그리도록 합니다. display 메소드는 창의 뷰 계층의 컨텐트 뷰부터 시작하여 각각의 뷰가 자신을 다시 그리게 합니다. 이 메소드는 원-샷 윈도(오프스크린이 되면 백킹 스토어가 릴리즈되는 윈도)에서 윈도가 스크린에 보여지기 전에 호출됩니다. 게다가, setFrame:display:에 두번째 인자를 YES로 하여 창이 리사이즈 하고 모든 뷰를 다시 그리도록 할 수 있습니다.
윈도 디스플레이와 관련된 기법에 윈도 업데이팅이 있습니다. 이벤트 루프의 모든 패스에서 NSApp은 업데이트 메시지를 윈도 리스트의 각 윈도에 보냅니다. update의 기본 구현은 아무 일도 하지 않지만, NSWindow의 서브클래스는 이 메소드를 오버라이드하여 응용프로그램의 상태를 조사하고 필요에 따라 윈도의 모양새 혹은 행동을 수정할 수 있습니다.
[편집] Window Status
응용프로그램의 각 창은 사용자의 인터랙션에 관련된 상태를 갖습니다. 창의 모양새는 이 상태를 지시해줍니다. 비활성화된 창은 열려있고 화면에 보일지라도, 전면에 위치하지는 않습니다. 비활성 창의 컨트롤과 타이틀 바의 타이틀은 흐리게 표시됩니다. 사용자는 이들 창을 클릭하여 전면으로 위치시키기 전에 이 창과 인터랙션할 수 없습니다.
활성 창은 현재 사용자 입력이나 주의의 포커스를 받는 창 입니다. 그들은 전면에 위치하며 컨트롤은 색상을 갖고 있고 타이틀은 검정색으로 표시됩니다. 활성 창은 메인과 키 두 개의 상태를 갖습니다. 현재 사용자의 주의의 포커스인 활성 창이 메인 윈도 입니다. 대부분의 경우 이 창들은 키 윈도이기도 합니다. 키 윈도는 현재 키보드 이벤트를 받는 창입니다.
그러나, 때로는 메인 윈도와 키 윈도가 다를 수 있습니다. 메인 윈도가 현재 사용자 주의의 포커스를 받고 있지만, 다른 창이 키 이벤트의 입력 포커스를 받을 수 있습니다. 키 윈도는 텍스트필드와 같은 객체가 있어서 키보드를 타이핑하는 것으로 사용자가 글자를 입력할 수 있어야 합니다. 이 경우, 키 윈도는 주로 다이얼로그와 패널로 메인 윈도에 관련된 특정 데이터를 사용자가 입력하게 됩니다. (예를 들어, Find 다이얼로그)
애플리케이션 객체(NSApp)은 응용프로그램의 창의 메인과 키 상태를 유지합니다. 창의 상태는 종종 창이 받고 디스패치하는 이벤트에 의해 결정됩니다. ( “More About Event Dispatch” 를 보십시오.)
[편집] Windows and Event Handling
NSWindow는 두 가지 기본적인 방법으로 이벤트와 이벤트 핸들링에 관여합니다. 이벤트 디스패치에서는 적극적으로 관여합니다. 다른 면에서는 제한된 이벤트 스트림의 수동적인 수취인입니다.
[편집] Event Dispatch
"More About Event Dispatch"에 설명된 것처럼, 응용프로그램은 받는 대다수의 이벤트를 그 이벤트가 속하는 NSWindow 객체에 sendEvent: 를 보내는 것으로 디스패치합니다. 결과로 윈도 객체는 그 이벤트를 받아야 하는 NSView 객체를 찾아 그 뷰에 적절한 NSResponder 메시지를 보내면서 NSEvent 를 보냅니다. 예를 들어, 키-다운 이벤트가 발생하면, keyDown:을 뷰로 보냅니다. 마우스-드래그 이벤트(좌측 버튼)이 발생하면 mouseDragged: 를 보냅니다. 일반적으로 윈도 객체가 타겟 뷰를 찾는 방법은 키와 마우스 이벤트 사이에 차이점이 있습니다.
- 윈도는 키 이벤트를 뷰 계층의 퍼스트 리스폰더에게 보냅니다.
- 윈도는 마우스 이벤트가 발생한 뷰에 마우스 이벤트를 보냅니다.
키 다운(NSKeyDown)과 마우스 왼쪽 누름(NSLeftMouseDown)이벤트가 가장 많은 처리를 요구합니다. NSWindow가 keyDown: 메시지를 퍼스트 리스폰더 뷰에 보내기 이전에 키 캐릭터(혹은 캐릭터들)을 시스템 입력 관리자(input manager)로 보내고, 시스템 입력 관리자는 입력을 삽입하거나 수행할 명령의 텍스트로 해석합니다. mouseDown: 메시지를 보내기 전에 NSWindow는 타겟 뷰를 퍼스트 리스폰더로 만들기를 시도합니다. 적절한 수식 키(modifier key)가 눌렸다면 메시지를 보내지 않고 컨텍스츄얼 헬프나 메뉴를 디스플레이하는 것으로 처리합니다.
[편집] Modal Windows
보통 응용프로그램은 이벤트를 사용자의 액션이 발생한 창에 우선적으로 가이드하면서 모든 창에 배포합니다. 그러나 응용프로그램은 창을 모달로 돌릴 수 있으며, 이 경우 창을 제거하기 전에 사용자가 작업을 완성하도록 요구합니다. (예를 들어, 파일을 선택하거나, 이름을 입력하거나 OK 버튼을 누르는) 모달 창은 Mac OS X에서 흔합니다. 에러 메시지 다이얼로그와 문서 열기와 프린팅을 위한 패널이 포함됩니다.
NSWindow 객체는 모달-윈도 기법에서 수동적인 참여자입니다. 프로그래밍으로 모달 행동을 시작하고 관리하는 것은 응용프로그램이 담당합니다. 창을 모달로 돌리기 위해서는 NSApp은 보통의 이벤트 루프 기법을 사용하지만 입력을 특정 창이나 패널로 제한시킵니다. 루프에서, 이벤트를 수집하지만 특정 범주(주로 모달 윈도와의 관련성)에 속하지 않는 이벤트는 버립니다.
NSApplication은 창을 모달로 돌리기 위한 방법을 몇가지 제공합니다.
- 블락킹 - 응용프로그램은 사용자가 모달 창을 제거하기 까지 블락합니다.
- 넌블락킹(모달 세션) - 응용프로그램은 모달 세션을 시작하고 윈도를 이벤트 루프가 한번 돌 동안 모달로 돌립니다. 모달 세션 코드는 특정 조건이 만족할 때까지 루프에서 윈도를 모달로 계속 돌릴 수 있습니다.
다른 Application Kit 클래스도 윈도와 패널을 모달로 돌리도록 하는 메소드를 제공합니다.
[편집] Panels
패널은 부차적인 창으로 응용프로그램이나 문서 창을 지원하는 역할을 합니다. 그들은 종종 다이얼로그라고 불려집니다. Cocoa에서 패널은 NSPanel의 인스턴스이거나 NSPanel의 서브클래스의 인스턴스입니다. 패널은 보조 기능에 적합하도록 특별한 행동을 가지고 있습니다. 그들은 키 윈도가 될 수는 있지만 결코 메인 윈도가 될 수 없습니다. 기본적으로, 응용프로그램이 비활성화가 되면스크린에서 제거되고, 응용프로그램이 다시 활성화되면 디스플레이 됩니다. (경고 다이얼로그는 이 행동에서 제외됩니다.) 또한, 패널이 반복적인 사용을 염두에 두고 있기 때문에 닫혀도 릴리즈되지 않습니다. 패널이 (유틸리티 창 처럼) 플로팅 윈도가 되도록 설정하여 프로그램의 다른 윈도보다 더 높은 레벨이 있도록 만들 수 있습니다.
[편집] Views
NSView 객체 (혹은 단순히 뷰)는 창에서 사각 영역을 차지합니다. Cocoa에서 뷰는 NSView의 서브클래스의 인스턴스입니다. 그들은 Application Kit에서 가장 흔한 종류의 객체입니다. Cocoa 응용프로그램에서 여러분이 보는 거의 모든 객체는 뷰 입니다. 뷰는 드로잉과 이벤트 처리에서 최전선이며 이해해야할 더 중요한 객체 타입중의 하나입니다.
스크린에서 드로잉 뷰는 객체 자신의 시각적인 표현입니다. 실제적인 측면에서 뷰는 자기 자신을 그립니다. 또한, 마우스, 키보드, 혹은 다른 입력 장치로부터의 입력에 응답하는 표면을 제공합니다.
[편집] Varieties of Views
NSView는 응용프로그램의 기본 드로잉, 이벤트 처리, 프린팅 아키텍쳐를 정의합니다. NSView 자체는 컨텐츠를 그리거나 사용자 이벤트에 반응하지 않으므로, 보통 NSView의 인스턴스와 직접 인터랙션하지 않습니다. 대신 커스텀 NSView의 커스텀 서브클래스의 인스턴스를 사용합니다. 커스텀 뷰 클래스는 NSView를 상속하고 Application Kit에 의해 자동으로 호출되는 많은 메소드를 오버라이드 합니다.
Application Kit의 클래스 계층을 보면(Figure 1-9), NSView로부터 직간접적으로 상속하는 클래스가 아주 많은 것을 알 수 있습니다.
- Controls 컨트롤은 응용프로그램에서 선택을 나타내기 위해 사용자가 다루는(클릭이나 드래깅 같은 것으로) 뷰 입니다. 버튼, 슬라이더, 텍스트필드, 스테퍼가 컨트롤의 예 입니다. 컨트롤은 보통(항상 그렇지는 않습니다) NSView에서 상속하지 않는 셀 객체와 함께 작동합니다. "Controls and Menus"에서 컨트롤에 대해 더 상세히 다루고 있습니다.
- Container views 몇몇 뷰는 다른 뷰나 더 원시적인 데이터를 포함하거나 표시하는데 사용되어집니다. 그들은 데이터를 수정하거나 유저 인터페이스의 더 효율적인 표현을 가능하게 할 수도 있습니다. 이들 종류의 뷰는 NSTextView, NSImageView, NSBox, NSSplitView와 NSTabView 객체가 있습니다.
- Compound views 몇몇 뷰는 다른 뷰들로 구성되어 있습니다. Cocoa 프로그램에서 텍스트 뷰를 보면, NSTextView 객체 뿐만 아니라 NSClipView 객체와 NSScrollView 객체도 포함하고 있습니다. (NSScrollView 객체는 NSScroll 객체도 있습니다.) 다른 예는 NSTableView의 인스턴스인 데이블 뷰 입니다. 테이블 뷰는 테이블 헤더와 테이블 컬럼(뷰가 아닙니다)을 위한 객체들로 구성되어 있습니다.
- Wrapper views Mac OS X 기술을 위한 Cocoa "호스트" 역할을 수행하는 약간의 뷰입니다. 이들 객체의 예는 NSOpenGLView와 NSMovieView 가 있습니다.
이들 카테고리의 객체 사이에는 약간 겹치는 부분이 있습니다. 예를 들어, NSTableView 객체는 복합 객체이지만 동시에 컨트롤입니다.
[편집] The View Hierarchy
창에 대한 앞의 논의("Windows")로부터 기억할 수 있는 것처럼, 각각의 뷰는 자신을 표시하는 창과 연관되어 있습니다. 창 안의 모든 뷰는 뷰 계층에 함께 연결되어 있습니다. 각각의 뷰는 다른 뷰를 자신의 슈퍼뷰로 가지며 슈퍼뷰는 서브뷰들을 가질 수 있습니다. 뷰 계층의 최상위에는 윈도의 컨텐트 뷰가 있으며 컨텐트 뷰에는 퍼블릭 슈퍼뷰가 없습니다. (프라이빗 슈퍼뷰는 있습니다) 뷰 계층의 핵심적인 시각적 특성은 포함입니다. 슈퍼뷰는 자신의 서브뷰를 포함하며 그에 따라 위치하게 됩니다. Figure 6-11은 이 포함을 묘사합니다.
Figure 6-11 View hierarchy

계층으로 뷰를 배열하는 것은 드로잉과 이벤트 핸들링 모두에 유익합니다. 드로잉은 다음 세가지 측면에서 혜택을 받습니다.
- 복잡한 뷰가 다른 뷰들로부터 구축되도록 합니다. 예를 들어, 그래픽 키패드는 각각의 키를 위한 분리된 서브뷰로 이루어진 컨테이너 뷰일 수 있습니다.
- 각각의 뷰가 편리한 드로잉을 위해 자신의 좌표계를 가질 수 있도록 합니다. 뷰는 그들 슈퍼뷰의 좌표내에 위치하며, 뷰 객체가 옮겨지거나 좌표계가 변형되면 그에 따른 서브뷰도 함께 옮겨지거나 변형됩니다/. 뷰가 자신의 좌표계에서 그려지기 때문에 드로잉 인스트럭션은 스크린에서 뷰의 위치나 슈퍼뷰가 옮겨지는 것과 상관없이 동일하게 유지됩니다.
- 드로잉 패스에서 렌더링되는 뷰의 레이어 순서를 정하는데 사용됩니다. ("Displaying a View"를 보십시오)
뷰 계층 (이제 뷰 인스턴스 계층을 의미합니다)은 리스폰더 체인에서 중요한 부분이기 때문에 이벤트 처리에서 중요한 역할을 담당합니다. 리스폰더 체인에 대한 더 많은 정보는 "Responders and the Responder Chain"에서 찾을 수 있습니다.
뷰 계층은 동적입니다. 응용프로그램이 돌아가는 동안, 뷰를 재배열하고, 추가하고, 제거할 수 있습니다. 한 창에서 뷰를 다른 창으로 옮길 수 있으며 뷰를 특정 계층에서 움직일 수도 있습니다.
NSView는 뷰 계층에서 뷰를 찾도록 도와주는 관계 프로퍼티를 세개 가지고 있습니다.
- window-뷰가 나타나는 윈도(NSWindow 객체)
- superview-계층에서 바로 위에 있는 뷰
- subviews-뷰가 포함하고 있는 뷰의 리스트(0~다수의 뷰를 가질 수 있습니다)
이들 프로퍼티를 반영하여 Figure 6-12는 창 객체와 뷰 객체의 관계를 도표화합니다.
Figure 6-12 Relationships among objects in a view hierarchy

[편집] View Geometry and Coordinates
뷰 지오메트리는 대부분 각 뷰와 관련된 두개의 사각형, 프레임과 바운드와 관련되어 있습니다. 이들 사각형이 동일한 영역을 나타내지만 다른 목적을 가지고 있습니다. 이 둘은 함께 뷰의 위치와 크기, 그림을 그리고 이벤트에 응답하는 좌표계를 정의하는데 도움을 줍니다.
[편집] The Frame
프레임 사각형은 뷰의 영역, 그림을 그릴수 있는 판을 정의합니다. 뷰를 창 위의 사각 영역이라고 생각한다면, 프레임은 이 사각형의 치수와 창에서의 위치를 정의합니다. 뷰는 프레임 안에만 그릴 수 있습니다. 기본값으로 Application Kit은 뷰가 프레임에 그리는 컨텐츠를 클리핑합니다.
Figure 6-13에서 나타난 것처럼, 뷰의 프레임 사각형은 슈퍼뷰의 프레임 바깥으로 확장될 수 있지만, 드로잉은 포함하는 조상 뷰의 체인에 클리핑됩니다. 스크린에서 보이는 뷰의 영역은 뷰의 프레임 사각형 내와 모든 조상 뷰의 프레임 사각형 내에 있는 부분입니다.
Figure 6-13 Hierarchical views

Figure 6-14는 계층적으로 관련된 뷰 세개를 보여줍니다. 이 경우는 중간 뷰가 슈퍼뷰의 프레임 사각형에 약간 벗어나 있습니다. 비록 맨 밑의 뷰가 슈퍼뷰에 다 들어가지만 일부가 조상 뷰 밖으로 벗어나 있어서 색으로 표시된 부분만 보이게 됩니다.
Figure 6-14 Views clipped by the superview

때로 뷰는 창이 표시할 수 있는 공간보다 더 많은 것을 포함할 수 있습니다. 예를 들어, 긴 문서 컨텐츠를 담고 있는 뷰를 생각할 수 있습니다. 그런 뷰는 다른 종류의 서브뷰, 더 작은 뷰로 일부만 보이게 될 수 있습니다. 이 더 작은 뷰는 클립 뷰(NSClipView의 인스턴스)로 알려져 있습니다. 포함된 스크롤 뷰(NSScrollView)와 스크롤러(NSScroller)의 도움으로 사용자는 클립 뷰 내에 문서가 보이는 부분을 컨트롤 할 수 있습니다. 서브뷰가 움직임에 따라 문서의 다른 부분이 뷰로 스크롤됩니다.
뷰의 서브뷰 내에서 뷰의 위치를 옮기고자 할때는 프레임의 원점을 재설정해줍니다. 뷰의 크기를 바꿀 때는 프레임 사각형의 크기를 변화시키면 됩니다. 이들 값은 슈퍼뷰의 좌표계에 따라 해석되기 때문에, 슈퍼뷰의 좌표가 변하면 스크린 상의 뷰의 크기와 위치도 변하게 됩니다.
뷰를 프레임 원점 주위로 회전시킬 수도 있습니다. 회전은 뷰의 형태나 크기에 영향을 미치지 않습니다. 회전하여 프레임의 측면이 슈퍼뷰의 x축과 y축에 평행하지 않아도 여전히 사각형의 형태를 유지합니다. 프레임의 원점은 프레임 회전의 각에 상관없이 변하지 않습니다. 회전된 뷰의 서브뷰는 뷰에 정렬된 상태로 유지되며 회전된 뷰의 슈퍼뷰와 연계하여 회전됩니다. Figure 6-15는 Figure 6-13의 동일한 세개의 뷰를 계층의 중앙에 위치한 뷰가 회전된 상태로 보여줍니다.
Figure 6-15 Rotated view and its subview

[편집] The Bounds
뷰의 프레임이 뷰를 포함하는 슈퍼뷰 내에서 뷰의 크기와 위치를 제공하는 반면, 드로잉에는 큰 쓸모가 없습니다. 뷰는 바운드 사각형으로 정의된 자신의 로컬 좌표 계에서 모든 드로잉과 이벤트 처리를 수행합니다.
뷰의 바운드 사각형은 뷰의 로컬 좌표계가 뷰의 영역에 매핑되는 방식을 정의합니다. 프레임 사각형의 물리적인 영역과 동일한 곳을 나타내지만, 이 영역은 뷰의 로컬 좌표계로 설명되어 있습니다. 기본적으로 뷰의 바운드 사각형은 프레임의 크기와 동일하며, (0.0, 0.0)의 원점을 갖습니다. 이런 배열 하에, 뷰는 자신의 컨텐츠를 양의 값을 이용한 좌표로 위치시키고 그립니다.
그러나 뷰가 뒤집히면 상황이 바뀝니다. 뷰는 자신의 좌표계를 뒤집어서 드로잉 원점을 뷰의 좌측 위가 되고 y축은 아래로 갈수록 값이 커지게 할 수 있습니다. Figure 6-16은 뒤집한 좌표계가 어떻게 보일지를 나타냅니다. 뒤집힌 뷰는 영어와 같이 문서의 좌측 위에서 시작하고 우측 아래로 점점 내려오는 텍스트를 그리는 응용프로그램에 특히 유용합니다.
Figure 6-16 Flipping a view

보통 뷰는 바운드 사각형을 사용하여 스크린에 렌더되지 않을 부분을 그리기를 시도하지 않도록 합니다. 조상 뷰의 바깥으로 벗어나면 드로잉이 클립되기 때문에, 바운드 사각형 그 자체로는 스크롤되지 않거나 모든 조상뷰의 프레임 사각형 안에서 움직이지 않는 뷰에서만 신뢰할 수 있는 가이드 입니다. NSView 클래스는 어디에 그릴지를 결정할 다른 프로그래밍 방법을 제공하지만 바운드 사각형은 어느 드로잉 계산에서든 일부 사용됩니다.
[편집] Drawing Coordinates
바운드 사각형은 뷰에 드로잉 좌표를 제공합니다. 뷰가 자신을 그리기 전에 그 좌표계가 응용프로그램의 현재 좌표계가 됩니다. (상세한 내용은 "How Views Get Drawn"을 참고하십시오) 뷰의 기본 좌표계는 다음 차이점을 제외하고는 슈퍼뷰와 동일합니다.
- 슈퍼뷰에서 뷰의 프레임을 위치시키는 지점은 바운드 사각형의 원점(0.0, 0.0)이 되고, 드로잉 좌표의 원점이 됩니다.
- 뷰의 프레임이 회전하면, 드로잉 좌표계도 함께 회전합니다. 바운드의 x축과 y축은 프레임의 축과 평행으로 유지됩니다.
그러나 변형이 바운드에 가해지면, 이들 상세 요소가 변할 수 있습니다.
Figure 6-17은 뷰의 기본 좌표계와 슈퍼뷰의 좌표계의 관계를 묘사합니다. 이 그림에서 innerView 객체는 슈퍼뷰의 좌표계의 (400.0, 200.0)에 위치하고 있습니다. innerView가 하는 모든 드로잉의 경우 동일한 지점이 좌표 원점 (0.0, 0.0)으로 여겨집니다. 이 예제에서 처럼, innerView가 (100.0, 200.0)에서 시작하는 텍스트를 그리면, 슈퍼뷰가 아닌 자신의 원점으로부터 이 지점이 계산됩니다. 비록 newView가 그림에서처럼 회전되더라도 바운드의 축은 프레임과 함께 회전합니다. 뷰의 드로잉 좌표의 원점은 변하지 않습니다.
Figure 6-17 Relation between coordinate systems of view and superview

뷰는 기본 좌표계를 여러 방식으로 수정할 수 있습니다.
- 프레임의 원점으로 정의된 지점 외에 다른 지점으로 원점을 이동할 수 있습니다.
- 바운드 축의 유닛 크기를 슈퍼뷰와 다르게 조절할 수 있습니다.
- 바운드 사각형의 축을 바운드의 원점 주위로 회전시켜 프레임의 축과 일치하지 않게 할 수 있습니다.
이들 수정은 뷰가 그림을 그리는 좌표계를 변화시켜 그려지는 것들의 모양에 영향을 미칠 수 있지만, 드로잉이 나타나는 영역을 바꾸지는 않습니다. 다른 말로 하면, 뷰의 프레임 사각형에는 영향을 미치지는 않습니다.
[편집] How Views Get Drawn
뷰는 Cocoa 프로그램에서 창의 컨텐츠를 그리는 주된 책임을 갖는 객체입니다. 다른 Application Kit 객체(NSCell, NSBezierPath, NSAttributedString 객체)도 그릴 수 있으나, NSView "호스트" 객체가 드로잉 표면을 제공하고 드로잉을 조정해 주어야만 합니다. 다음 섹션에서는 Application Kit이 뷰의 드로잉을 조정하는 방식에 대한 고수준의 개요를 제공합니다.
[편집] Displaying a View
뷰는 보통 자신이 원할때 자기를 그리지는 않습니다. 반드시 무효한(혹은 "더러운") 상태로 표시되고 디스플레이가 필요하다고 명시되어져야만 합니다. 뷰를 다시 그리는 것은 (NSWindow 자동-디스플레이 기능이 켜져 있는 경우는) 즉시 발생할 수 있으며, 메인 이벤트 루프의 현재 사이클의 종료까지 연기될 수도 있습니다. 윈도는 자동-디스플레이가 기본적으로 활성화 되어 있습니다. (자동-디스플레이는 "The Main Event Loop"과 "Windows and Drawing"에 설명되어 있습니다.)
NSView의 디스플레이 메소드 (이들 메소드는 이름에 "display"가 포함되어 있습니다)중의 하나를 이용하여 뷰나 뷰의 일부를 즉시 다시 그릴 수 있습니다. 이들 메소드는 여러 측면에서 차이점이 있지만, 결과적으로는 Application Kit이 다음 작업을 수행하는 결과를 낳습니다.
1. 무효화된 뷰에 포커스를 잠급니다. ("Locking Focus"에 설명되어 있습니다.)
2. 뷰의 drawRect: 메소드를 호출합니다. ("What Happens in drawRect:에 설명되어 있습니다.)
3. (만일 윈도의 백킹 스토어가 double-buffered 경우) 드로잉 패스의 마지막 부분에서 뷰에 관련된 윈도를 플러쉬합니다.
대부분의 경우 뷰를 즉시 디스플레이 하는 것보다 자동-디스플레이 기법을 사용하고 이벤트 사이클에서 뷰(혹은 뷰의 일부)가 디스플레이가 필요하다고 표시하는 방법이 추천됩니다. 뷰와 연관된 NSWindow는 표시된 뷰를 모아 뷰 계층의 위치에서 가장 상위의 객체가 먼저 오도록 리스트화합니다. 이벤트 사이클의 끝에서 이 리스트를 한번의 드로잉 패스에서 재귀적으로 처리하며 각 뷰를 차례로 포커스를 잠그고, 디스플레이가 필요하다고 표시된 뷰에게 전체 혹은 뷰의 일부를 드로우하도록 요청합니다. 모든 뷰가 그려지고 난 후, 창은 (버퍼가 되었다면) 플러쉬됩니다.
Application Kit은 응용프로그램이 명시적으로 재디스플레이가 필요하다고 표시한 뷰 외에 추가적인 뷰와 뷰 영역을 그리도록 요청할 수 있습니다. 이 추가적인 뷰 드로잉이 해당하는 창 영역의 전체를 업데이트하는데 필요하다고 결정할 수 있습니다. 이것은 뷰를 그리는데 중요한 측면인 뷰의 불투명도 때문입니다. 뷰가 표면의 모든 비트를 그릴 필요는 없지만, (isOpaque가 YES 를 반환하도록 구현하는 것으로) 자신이 불투명하다고 선언해야 합니다. 뷰를 디스플레이하도록 표시할 때, Application Kit은 뷰의 불투명도를 체크하고, 불투명하지 않다면(일부가 투명하다면) Application Kit은 뷰 계층을 타고 올라가서 불투명한 슈퍼뷰를 찾습니다. 원래 뷰에 의해 가려지는 불투명한 조상의 부분을 계산합니다. 그 후, 이 뷰 부터 원래 지저분하다고 표시된 뷰까지 계층을 타고나가며 그립니다. 만일 Application Kit이 뷰를 그리기 전에 맨 처음 불투명한 조상을 찾는 것을 막으려면 몇몇 "불투명도 무시하는 디스플레이" 메소드를 사용하면 됩니다. (이들 메소드는 Table 6-1과 Table 6-3 에 나와 있습니다)
또한 뷰의 일부와 그들의 서브뷰를 디스플레이가 필요하다고 표시하고, 자동-디스플레이 기법이 작동하기를 기다릴 필요 없이 즉시 그려지도록 할 수 있습니다. 이런 기능을 제공하는 NSView 디스플레이 메소드는 모두 displayIfNeeded...로 시작합니다. 비록 디스플레이가 즉시 일어날지라도, 이들 메소드는 display나 displayRect: 메시지를 고립되어있는 뷰에 보내는 것보다 더 효율적입니다.
Table 6-1은 즉시 각개 뷰나 뷰의 영역을 디스플레이하는 NSView 디스플레이 메소드를 보여줍니다.
Table 6-1 NSView display methods—immediate display
| 디스플레이 영역과 불투명도 | 디스플레이 메소드 |
|---|---|
| 전체 뷰 | display |
| 일부 뷰 | displayRect: |
| 전체 뷰, 불투명도 무시 | None |
| 일부 뷰, 불투명도 무시 | displayRectIgnoringOpacity: |
Table 6-2는 자동-디스플레이 기능을 사용하여 뷰나 뷰의 영역이 다시 그려지도록 표시하는 메소드의 목록입니다.
Table 6-2 NSView display methods—deferred display
| 디스플레이 영역 | 디스플레이 메소드 |
|---|---|
| 전체 뷰 | setNeedsDisplay: |
| 일부 뷰 | setNeedsDisplayInRect: |
Table 6-3은 Table 6-2의 메소드로 무효화된 뷰(혹은 뷰의 일부)를 즉각적으로 디스플레이하는 메소드의 목록입니다.
Table 6-3 NSView display methods—Immediate display of marked views
| 디스플레이 영역과 불투명도 | 디스플레이 메소드 |
|---|---|
| 전체 뷰 | displayIfNeeded |
| 일부 뷰 | displayIfNeededInRect: |
| 전체 뷰, 불투명도 무시 | displayIfNeededIgnoringOpacity |
| 일부 뷰, 불투명도 무시 | displayIfNeededInRectIgnoringOpacity: |
여러번 반복하는 경우, 뷰를 즉시 디스플레이하는 경우는 자동-디스플레이 기능을 사용하는 것보다 덜 효율적이며, 표시된 부분만 즉시 디스플레이하는 것은 이 둘의 중간쯤 됩니다. 전체 뷰 보다는 뷰의 일부를 지저분하다고 표시하는 것이 일반적으로 더 효율적입니다. 디스플레이 메소드는 응용프로그램 개발에 상당한 편의를 제공합니다. 뷰는 포커스를 잠그고, 자신을 그리고, 포커스를 풀 수 있습니다. 그러나, 이것은 타이머 콜백에 움직이는 컨텐츠와 같이 특별한 경우에만 추천됩니다. 보통은, Application Kit의 디스플레이 기법을 우회해서는 안됩니다.
[편집] Locking Focus
Application Kit 이나 여러분의 코드가 뷰에 포커스를 잠그면, NSView는 다음의 셋업 절차를 완성합니다.
- 뷰의 좌표계를 슈퍼뷰의 좌표계로부터 변형하여 현재 응용프로그램의 좌표계가 되도록 합니다.
- 뷰가 그릴 수 없는 사각형의 외부 영역에 클리핑 패스를 생성합니다.
- 현재 그래픽스 스테이트의 다른 파라미터를 활성화하여 뷰의 드로잉 환경을 설정합니다.
lockFocus (혹은 관련된 lockFocus...) 메시지를 뷰에 보내어 뷰에 포커스를 잠급니다. 포커스된 뷰에 드로잉이 끝나면, unlockFocus를 보내어 포커스를 풀어줍니다. Application Kit은 뷰에 drawRect: 메시지를 보내면 자동으로 포커스를 잠그고 풀어줍니다.
포커스된 뷰는 현재 드로잉이 행해지는 뷰 입니다. 모든 창은 윈도 그래픽스 컨텍스트를 갖고 있는데, 이 컨텍스트는 드로잉을 위한 윈도 서버 목적지를 정의합니다. 이 그래픽스 컨텍스트는 하나 혹은 그 이상의 그래픽스 스테이트를 포함하고 있으며 이들 각각은 특정 뷰의 드로잉 오퍼레이션을 분리시킵니다. 현재 그래픽스 스테이트는 가장 최근에 포커스된 뷰의 유효한 좌표계와 다른 드로잉 파라미터를 포함합니다.
그래픽스 스테이트는 현재 트랜스포메이션 매트릭스를 포함합니다. 이 트랜스포메이션 매트릭스는 회전, 스케일링, 이동 작업을 통해 한 좌표 공간의 점을 다른 좌표의 공간으로 매핑하는데 사용되는 수학적인 구조입니다. 그래픽스 스테이트는 현재 클리핑 영역과 이미지를 그리는데 드로잉 작업이 사용할 수 있는 다른 파라미터를 정의합니다. 이들 파라미터에는 다음이 포함됩니다.
- 현재 채움, 스트로크 작업의 색상
- 알파 값 (투명도)
- 두께, 조인, 캡, 대쉬, 미터 리미트를 포함한 선 애트리뷰트
- 안티앨리어싱 값
- 현재 컬러 스페이스
- 텍스트 애트리뷰트: 폰트, 폰트 크기, 글자 간격
- 블렌드 모드
다른 뷰가 이미 포커스를 가지고 있을 때, 뷰에 포커스를 잠그는 것도 가능합니다. 사실 이것이, 드로잉 패스에서 Application Kit이 수행하는 것입니다. 각 뷰는 자신의 좌표계를 수퍼뷰의 좌표계의 수정본으로 갖고 있기 때문에, 보통 뷰는 자신의 슈퍼뷰 다음으로 포커스에 들어오게 됩니다. 무효화된 뷰의 그룹에서, Application Kit은 최상위의 뷰의 포커스를 잠그고 뷰 계층을 따라 내려가며 네스트된 lockFocus 호출을 수행합니다. 연속적인 각 뷰에 포커스가 잠기면서, 현재 그래픽 스테이트는 그래픽스 컨텍스트가 유지하는 스택에 저장됩니다. (Figure 6-18을 보십시오) 포커스가 뷰에서 풀리면, 스택의 맨 위에 있는 그래픽스 스테이트가 나와서 현재 그래픽스 스테이트로 복원됩니다.
Figure 6-18 Nested focused views and graphics state stack

[편집] What Happens in drawRect:
Application Kit이 뷰에 포커스를 잠근 후, drawRect: 메시지를 보냅니다. 뷰의 클래스는 이 메소드를 구현하여 뷰를 그립니다. 사각형이 drawRect:에서 전달되어 뷰의 좌표계가 그려질 영역을 지정합니다. 이 사각형은 뷰의 바운드에 일치할 수도 있고 아닐 수도 있습니다. 이 사각형은 무효하다고 표시된 뷰의 모든 사각 영역의 연합이거나 그것의 슈퍼셋일 수 있습니다.
드로잉 인스트럭션과 데이터를 윈도 서버에 보내는 것은, 특히 결국 보여지지 않을 드로잉의 경우, 가능하면 피해야할 비용이 듭니다. Application Kit의 주요 최적화는 드로잉이 복잡한 경우 특히 그려질 영역을 제한하는 것입니다. 뷰는 전체를 그릴지 (가장 비효율적인 선택방법), 아니면 넘겨진 사각형에 정의된 영역을 그릴지 선택할 수 있습니다. 잠재적으로 더 효율적인 절차는 (NSView의 getRectsBeingDrawn:count:로) 무효화된 영역의 리스트를 얻어와서 차례로 이들 각 영역을 선택적으로 그리는 것입니다.
drawRect:의 구현에서 뷰 클래스는 다양한 함수와 메소드를 호출하여 윈도 서버로 보내질 드로잉 인스트럭션과 데이터를 지정합니다. Application Kit은 다음의 고수준 드로잉 함수와 메소드를 제공합니다.
- 사각형을 그리고, 채우고, 지우고, 다른 작업을 수행하는 (NSGraphics.h에 선언된) 드로잉 함수
- 베지에 패스로 선이나 형태를 구성하는 메소드(NSBezierPath 클래스)
- 이동, 크기조절, 회전 작업과 관련된 아핀 트랜스폼을 생성하고 수행하는 메소드(NSAffineTransform 클래스)
- 색과 칼라스페이스 메소드(NSColor, NSColorSpace)
- 이미지 생성과 합성을 위한 메소드 (NSImage 및 다양한 이미지 표현 클래스들)
- 텍스트를 그리기 위한 메소드(NSString과 NSAttributedString)
Application Kit은 이들 메소드와 함수를 Core Graphics(Quartz) 함수와 타입을 이용해서 구현합니다. 뷰도 이들 Core Graphics 함수를 네이티브하게 사용하여 자신을 그릴 수 있습니다. 이들 Quartz 클라이언트 라이브러리 함수들은 윈도 서버의 렌더링 오퍼레이터에 직접 매핑하여 래스터(비트맵) 이미지가 윈도 내 드로잉에서 윈도의 백킹 스토어의 일부가 되게 합니다.
[편집] Threads and Drawing
뷰의 드로잉은 메인 쓰레드에서 일어나야 하지는 않습니다. 응용프로그램의 각 쓰레드는 뷰의 포커스를 잠그고 그릴 수 있습니다. 그러나 다음의 조건들이 있습니다.
- NSView 객체의 프로퍼티(예를 들어 프레임 사각형)의 수정은 메인 쓰레드에서만 일어나야 합니다.
- NSView 디스플레이 메소드가 호출될 때 Application Kit은 받는 뷰의 윈도에서 드로잉을 위한 락을 쥐게 됩니다. 이 디스플레에 메소드가 반환하기 전까지 어떤 커스텀 드로잉도 수행할 수 없습니다. 이것은 주어진 창에 한번에 단 하나의 쓰레드만 그릴 수 있다는 것을 의미합니다.
[편집] Views and Printing
뷰는 Cocoa 프린팅 아키텍쳐의 기초입니다. 이들은 스크린에 디스플레이될 컨텐츠를 제공하는 것처럼 프린트 되어질 컨텐츠를 제공합니다. 일반적인 절차는 동일합니다. Application Kit이 뷰의 포커스를 잠그고, drawRect: 메소드를 호출한 후, 뷰는 프린트 가능한 컨텐츠를 그리고 포커스의 잠김은 풀리게 됩니다. print: 메소드를 호출하여 뷰가 자신을 인쇄하도록 할 수 있습니다.
[편집] Views and Events
응용프로그램에서 뷰는 마우스 클릭이나 키 누름과 같은 대부분의 사용자 이벤트에 직접 응답하는 객체입니다. 그들은 거의 항상 사용자 이벤트가 발생하는 표면을 제공하는 객체입니다. 결과로, 가장 앞에 있는 이들 객체는 이벤트 메시지를 가장 먼저 처리할 기회를 갖습니다.
"Windows and Event Handling"에서 논의된 것처럼, 윈도는 사용자 이벤트를 (이벤트 메시지로) 이벤트를 받아야하는 뷰 계층의 뷰로 포워딩합니다. 마우스 이벤트의 경우는, 이벤트가 발생한 곳 아래의 뷰에 메시지를 보냅니다. 만일 이벤트가 키 이벤트라면, 윈도는 메시지를 퍼스트 리스폰더에 보내는데 보통은 키 포커스를 갖고 있는 뷰입니다. 키 이벤트를 받기 위해서, 뷰는 반드시 퍼스트 리스폰더 상태를 받아들인다고 선언해야 합니다. (그것은, NSRespnder의 acceptsFirstResponder메시지를 오버라이드하여 YES를 반환하도록 하는 것입니다.)
뷰는 리스폰더 객체로, NSResponder 클래스의 프로그래밍 인터페이스를 상속합니다. 이벤트 메시지는 이 클래스에 의해 선언된 메소드를 호출합니다. 이런 메시지의 예는 mouseDown:, mouseMoved:, keyUp:이 있습니다. 이벤트를 처리하려면, 뷰의 클래스는 반드시 적절한 NSResponder 메소드를 구현해야 합니다. 구현에서 건네받은 (이벤트에 대한 정보를 캡슐화하고 있는)NSEvent 객체를 조사하고 거기서부터 진행할 수 있습니다. 만일 뷰가 이벤트를 처리하지 않는다면, 리스폰더 체인에서 다음 리스폰더(보통 슈퍼뷰 )가 이벤트를 처리할 기회를 얻게 됩니다. "Responders and the Responder Chain"에서 리스폰더와 이벤트가 리스폰더 체인을 타고 올라가는 방식을 설명합니다.
뷰는 액션 메시지에서도 중요한 역할을 수행합니다. 이들 메시지는 종종 이벤트 메시지로 발생하여 NSControl 객체로 보내집니다. 이들을 처리하기 위해, 컨트롤 객체는 메시지를 타겟 객체에 액션 메시지를 보냅니다. 만일 타겟 객체가 지정되어 있지 않다면, 응용프로그램은 리스폰더 체인을 올라가며 이 액션 메시지에 응답할수 있는 객체를 찾습니다. NSControll 객체와 그들이 사용하는 NSCell 객체에 대한 더 많은 정보는 "Controls and Menus"를 참고하십시오.
[편집] Responders and the Responder Chain
NSApplication, NSWindow, NSView 이 코어 애플리케이션 클래스들의 객체들은 리스폰더 입니다. 이들은 NSResponder를 직간접적으로 상속하는 클래스들의 인스턴스 입니다. (Figure 6-19를 보십시오.) 이 추상 클래스는 이벤트에 응답할 수 있는 객체들의 예상된 행동과 인터페이스를 정의합니다. NSResponder의 서브클래스는 이 행동을 전체 혹은 일부 구현합니다.
Figure 6-19 NSResponder and its direct subclasses

NSResponder 클래스는 코어 애플리케이션 아키텍쳐의 세가지 주요 패턴 혹은 기법을 위한 인터페이스를 정의합니다.
- 이벤트 메시지(마우스 클릭이나 입력에 의해 발생하는 사용자 이벤트)를 처리하기 위한 몇몇 메소드를 선언합니다.
- 표준 키 바인딩으로 묶여있는 액션 메시지를 처리하기 위한 십수개의 메소드를 선언합니다.(예를 들어, 텍스트 내 삽입 지점을 옮기는 것) 액션 메시지는 타겟 객체로 디스패치 됩니다. 만일 타겟이 지정되지 않는다면, 응용프로그램은 적절한 리스폰더를 찾습니다.
- 응용프로그램 내에서 리스폰더를 지정하고 관리하는 메소드를 정의합니다. 이들 리스폰더는 리스폰더 체인이라는 것을 형성합니다. 리스폰더 체인은 이벤트와 액션 메시지가 그것을 처리할 수 잇는 적절한 객체를 찾기까지 찾아다니는 리스폰더의 시리즈 입니다.
리스폰더 체인은 Application Kit의 이벤트 핸들링을 위한 중심 기법입니다. 이벤트나 액션 메시지가 따라서 전달되는 연결된 리스폰더 객체의 시리즈 입니다. Figure 6-20에 묘사된 것처럼 리스폰더 객체가 이벤트나 액션을 처리할 수 없다면, 즉, 메시지에 응답하지 않거나 이벤트를 인식하지 못한다면, 체인 안의 다음 리스폰더에게 메시지를 다시 보냅니다. 메시지는 체인을 타고 올라가 처리될 때 까지 높은 수준의 객체들에게 전달됩니다. (처리되면, 버려집니다)
Figure 6-20 The responder chain

Application Kit이 응용프로그램에서 객체들을 구성할 때, 각 창의 리스폰더 체인을 설정합니다. 리스폰더 ㅊ인에서 필수적인 객체는 NSWindow 객체와 그 뷰 계층입니다. 계층에서 아래에 있는 뷰는 상위 레벨에 있는 객체들보다 먼저 이벤트나 액션 메시지를 처리할 기회를 얻게 됩니다. NSWindow는 퍼스트 리스폰더에 대한 레퍼런스를 유지하는데, 보통 창에서 현재 선택된 뷰이며 메시지에 반응할 기회를 가장 먼저 얻게 됩니다. 이벤트 메시지의 경우는, 다른 다음 리스폰더가 NSWindow 객체에 더해질 수는 있지만 보통 이벤트가 발생한 창을 관리하는 NSWindow객체에서 리스폰더 체인이 끝나게 됩니다.
액션 메시지의 경우에는 리스폰더 체인이 더 복잡합니다. 액션 메시지를 위한 리스폰더 체인을 결정하는 두가지 요인이 있습니다.
- 만일 응용프로그램이 현재 메인 윈도와 키 윈도를 모두 가지고 있다면, 두 창의 리스폰더 체인 모두 연관되어 먼저 키 윈도의 리스폰더 체인이 액션을 처리할 기회를 얻게됩니다. 각 윈도의 체인 마지막에, NSWindow 델리게이트가 응답할 기회를 얻게 됩니다. 결합된 리스폰더 체인의 마지막에는 NSApp과 그 델리게이트가 있습니다.
- 응용프로그램의 종류, 단순 창인지, 문서 기반인지, 아니면 윈도 컨트롤러를 사용하는 애플리케이션인지가 체인 내의 리스폰더 객체의 종류와 위치를 결정합니다.
NSResponder 클래스는 에러 표시와 리커버리,, 메시지 디스패치, 응용프로그램 도움말, 그외의 기능을 위한 메소드 선언을 포함합니다.
[편집] Controls and Menus
응용프로그램에서 여러분이 보는 많은 객체들은 의도를 전달하기 위해 다룰 수 있습니다. 이들 객체는 버튼, 체크박스, 슬라이더, 테이블뷰, 파일-시스템 브라우저, 메뉴(응용프로그램 메뉴와 팝업 메뉴 포함)등을 포함합니다. Cocoa에서는 선택할 수 있는 두 종류의 구현-컨트롤과 메뉴-의 기반에 비슷한 아키텍쳐가 있습니다. 이들 아키텍쳐에서 NSView 객체를 포함한 다양한 종류의 객체들은 함께 작동하여 사용자의 선택이나 의도의 지시를 가능하게 합니다.
[편집] Control and Cell Architecture
컨트롤은 유저 인터페이스 객체로 마우스 클릭과 같은 사용자 이벤트에 반응하여 프로그램의 다른 객체에 메시지를 보냅니다. 컨트롤의 흔한 타입은 버튼, 슬라이더, 텍스트 필드(보통 사용자가 리턴 키를 누를때 메시지를 보냅니다.)가 있습니다. 좀 덜 분명한 다른 종류의 컨트롤러는 테이블 뷰, 데이타 브라우저, 컬러 웰이 있습니다.
컨트롤은 추상 NSControl의 서브클래스의 인스턴스 입니다. 보통 하나 혹은 그 이상의 추상 NSCell의 서브클래스의 인스턴스인 셀을 관리합니다. 만일 Application Kit 클래스 계층(Figure 1-9)를 본다면, NSView의 서브클래스인 NSControl이 상당히 큰 컨트롤 클래스 가지의 뿌리임을 볼 수 있습니다. (NSButton, NSStepper, NSTextField 등등이 포함됩니다.) 계층에서 전혀 다른 위치에서(NSObject의 아래에서) NSCell은 대부분 컨트롤 클래스에 해당하는 셀 클래스들의 가지로 시작됩니다. (NSButtonCell, NSStepperCell, NSTextFieldCell 등이 있습니다.)
[편집] Controls That Manage Multiple Cells
Application Kit의 대부분의 컨트롤은 하나의 셀을 관리합니다. 이런 경우, 컨트롤은 셀이 실제 작업의 대부분을 수행하도록 합니다. 받은 메시지의 상당수를 셀로 보냅니다. 그러나 몇몇 컨트롤은 다수의 셀을 관리합니다. Application Kit은 컨트롤이 다수의 셀을 관리하는 경우에 두가지 일반적인 접근법을 취합니다.
- 하나의 셀 인스턴스가 드로잉의 템플릿으로 사용됩니다. 컨트롤이 셀을 그려야 할 때는 언제나 이 인스턴스를 사용하여 내용만 다른 각각의 셀 표시를 복제하는데 사용되어집니다. NSTableView 와 NSOutlineView 클래스가 테이블 뷰나 아웃라인 뷰 컬럼의 셀을 그릴때 이 접근법을 취합니다.
- 개개의 셀 인스턴스가 컨트롤에 그려지는 각 셀 영역을 표시합니다. NSMatrix 클래스는 셀 인스턴스들에게 자신을 그리도록 요청할 때, 이러한 접근법을 사용합니다. NSBrowser와 NSForm 객체도 비슷한 식으로 작동합니다. 이들의 셀도 개개의 인스턴스입니다. NSMatrix 객체(혹은 매트릭스)는 대다수의 셀 형식을 다룰 수 있습니다. 셀들을 가상의 면적의 그리드로 배열합니다. Interface Builder에서 셀의 매트릭스를 구성할 때, 셀은 주어진 셀 프로토타입의 복사본들입니다. 그러나, 프로그래밍을 통해 이 셀들이 다른 NSCell 서브클래스들의 인스턴스가 되게 할 수 있습니다. NSBrowser와 NSForm 컨트롤은 다룰 수 있는 셀의 타입이 더 제한되어 있습니다.
단일-셀 컨트롤이 중복된 디자인 같아 보일 수 있지만, 여기서의 가치는 컨트롤이 아니라 셀에 있습니다. “Rationale for the Control-Cell Architecture” 에서 설명을 보십시오.
[편집] How Controls Manage Cells
컨트롤은 모든것을 다 갖춘 NSView 객체입니다. 그들은 디스플레이가 필요하다고 표시될 수도 있으며, 자신을 그릴 책임이 있고, 사용자 이벤트에 응답할 수 있으며, 리스폰더 체인에 위치합니다. 런타임 시에, 컨트롤은 여느 뷰나 다름없이 행동합니다. Application Kit은 컨트롤의 drawRect: 메소드를 호출하여 컨트롤이 자신을 다시 그리도록 합니다. 만일 이벤트 메시지가 리스폰더 체인에 디스패치되고 컨트롤이 적절한 NSResponder 메소드를 구현했다면, 컨트롤은 그 이벤트를 다를 기회를 얻을 수 있습니다. 그러나, 자신이 직접 드로잉과 이벤트 처리를 하는 대신, 컨트롤은 이런 책임을 셀에게 넘기게 되고, 다수의 셀을 갖게 되면, 셀들의 행동도 조정합니다.
Figure 6-21에 묘사된 것처럼, 컨트롤은 자신의 drawRect: 메소드에서 보통 drawWithFrame:inView:를 셀에 보내어 전달된 사각형 내에서 자신을 다시 그리도록 요청합니다. 포커스가 이미 컨트롤에 잠겨 있기 때문에, 셀은 호스트 뷰의 표면을 사용하여 자신을 그릴 수 있습니다. 컨트롤은 mouseDown: 이벤트 메소드 구현에서 trackMouse:inRect:ofView:untilMouseUp:을 이벤트가 발생한 셀에 보냅니다. 보통, 셀은 마우스 이벤트가 나가거나 mouseUp: 이벤트를 받을 때 까지 자신의 경계 내에서 마우스 이벤트 트랙합니다. 그 후, 적절하게 응답합니다.(다른 종류의 이벤트 타입도 비슷한 방식으로 다뤄집니다.)
Figure 6-21 A control coordinating the drawing of a cell

셀이 자기 자신을 그릴때, 두가지 측면을 표현해야 합니다. 먼저, 동일 클래스의 셀 사이에 동일한 셀의 일반적인 외관입니다. 이들 특징에는 셀의 형태, 배경색, 스타일, 셀 상태를 나타내는 시각적인 표시(예를 들어, 셀이 활성화되지 않은 경우, 셀 선택을 나타내는 체크박스나 회색 텍스트)가 포함됩니다. 두번째 측면은 셀을 각각 구별해주는 셀의 컨텐츠 입니다. 컨텐츠를 위해 셀은 보통 타이틀(스트링)이나 이미지를 가지며, 둘 모두를 갖는 경우도 있습니다. 타이틀은 지정된 폰트를 가질 수 있습니다. (NSSliderCell과 같은 몇몇 셀은 이미지나 타이틀 대신 커스텀 컨텐츠를 갖습니다.)
셀은 컨텐츠(혹은 컨텐츠의 일부)로 객체 값이나 뭔가를 나타내는 객체를 가질 수 있습니다. 객체 값은 스트링으로 형식화 될수 있는 객체여야 하며, 셀 타이틀로 나타날 수 있습니다. (예를 들어, float 값을 캡슐화하는 NSNumber 객체) 대리 객체는 디스플레이 되지는 않지만 셀과 연관되어 있습니다. 예를 들어, "빨강색" 이라는 타이틀을 가진 버튼은, NSColor를 나타내는 대리 객체를 가질 수 있습니다. 객체 값과 대리 객체에 대한 더 많은 정보는 “Represented Objects” 에서 얻을 수 있습니다.
대부분의 셀을 구분짓는 다른 요소는 그들이 액션 메시지를 위해 캡슐화하는 정보입니다. (“The Target-Action Mechanism”에서 설명된) 타겟-액션 기법은 사용자가 컨트롤을 활성화 시킬때(예를 들어, 버튼을 누르거나 텍스트 필드에서 리턴 키를 누르면) 지정된 객체에 컨트롤이 메시지를 보내도록 합니다. 액션은 호출할 메소드를 찾는 셀렉터 입니다. 타겟은 지정된 객체입니다. (타겟이 nil로 지정되면 응용프로그램이 메시지를 처리할 수 있는 객체를 리스폰더 체인에서 찾게됩니다. “Responders and the Responder Chain”을 보십시오.) 추상 NSActionCell 클래스는 액션 셀렉터와 타겟 객체의 레퍼런스를 저장하고 받는 인터페이스를 정의합니다.
[편집] Rationale for the Control-Cell Architecture
Application Kit의 컨트롤-셀 아키텍쳐는 NeXTSTEP의 초기 때부터 역사적인 뿌리를 갖고 있습니다. 그러나 처음에는 필요성이 잘 이해되지 않을 수 있습니다. 컨트롤이 셀을 관리할 필요가 왜 있을까요? 왜 컨트롤 자신이 직접 필요한 작업을 수행하지 않을까요?
컨트롤이 그렇게 할 수 없는 이유는 없으며, 직접 커스텀 클래스를 디자인 하려 한다면, 셀이 없는 컨트롤 역시 유효한 접근법입니다. 그러나 컨트롤-셀 아키텍쳐는 몇몇 장점들을 갖고있습니다.
- 아키텍쳐는 컨트롤의 유용성을 확장시켜줍니다. 테이블 뷰나 매트릭스 같은 컨트롤은 각 타입을 따로 알고 있을 필요 없이 많은 다른 타입의 셀을 효율적으로 다룰 수 있습니다. NSButtonCell 객체와 같은 셀은 NSButton 컨트롤과 동작하도록 디자인 되었을 수 있지만, 매트릭스나 테이블 뷰 객체와도 동작합니다. 셀은 뷰의 다른 종류의 그래픽 객체들을 쉽게 관리하도록 해주는 추상적인 개념입니다. 셀은 플러그-인 디자인과 비슷하게 컨트롤이 타겟과 액션 정보를 포함해서 자신을 나타내는 정보를 가지고 있는 다양한 종류의 그래픽 객체를 호스트하도록 해줍니다.
- 컨트롤-셀 아키텍쳐는 예를 들어, 뷰와 서브뷰의 컬렉션 사이의 커플링보다, 컨트롤과 셀 사이의 커플링을 더 긴밀하게 해줍니다. 서브뷰는 대개 그들의 슈퍼뷰 내에서 자율적입니다. 컨트롤은 셀의 코디네이터로 더 잘 작동합니다. 예를 들어, 라디오 버튼의 매트릭스에서 컨트롤은 언제나 단 하나의 버튼만 켜져있도록 합니다.
- 드로잉 셀 영역(드로잉을 위해 몇몇 셀 인스턴스를 "고무도장"처럼 재사용합니다)을 위한 NSTableView 모델은 잠재적으로 수가 제한 없는 하위영역을 다뤄야 하는 컨트롤에 특히 효율적입니다.
- 드로잉의 템플렛으로 셀 인스턴스를 사용하는 것이 가능하지 않은 경우에도, 셀은 종종 서브뷰에 비해 더 높은 성능을 제공합니다. 뷰는 메모리와 계산의 측면 모두 상대적으로 무거운 객체입니다. 예를 들어, 유효하지 않은 뷰 영역을 뷰 계층에서 추적하고 전달하는 것은 무리한 비용을 요구합니다.
서브영역을 드로잉하는데 셀과 서브뷰를 사용하는 것은 각각 장단점이 있습니다. 뷰 무효화 기법이 없기 때문에, 컨트롤은 반드시 어떤 셀이 자신을 그릴 필요가 있는지 알아낼 책임을 져야 합니다. 그러나 뷰는 일반 목적 객체이기 때문에 보통 특화된 컨트롤이 필요한 계산을 더 효과적으로 수행할 수 있습니다.
[편집] Menu Characteristics and Architecture
컨트롤과 셀 뿐만 아니라, 메뉴를 사용하여 사용자는 (운영체제 뿐만 아니라) 응용프로그램에 자신의 의도를 전달할 수 있습니다. 메뉴는 간단한 단어로 구성된 선택(혹은 메뉴 항목)의 목록으로 서브메뉴라고 불리는 네스트된 메뉴를 포함할 수 있습니다. 사용자는 보통 (다른 방법이 존재하지만) 메뉴 항목을 클릭하여 선택합니다. 그 결과로 활성화된 응용프로그램이나 운영 체제에서 커맨드가 실행됩니다.
Cocoa는 다양한 종류의 메뉴를 지원합니다. 이들 중 가장 중요한 것은 응용프로그램에 특정한 메뉴로, Cocoa에서는 집합적으로 메인 메뉴라고 알려져 있습니다. 런타임시에, 응용프로그램에 특정한 메뉴는 응용프로그램 메뉴(응용프로그램의 이름으로 된 메뉴)와 그 우측에 있는 Help 메뉴까지 모든 메뉴를 포함합니다. 응용프로그램에 특정한 메뉴는 Apple 메뉴와 응용프로그램 특정 메뉴들의 우측에 위치하는 특정 서비스의 메뉴인 기타 메뉴와 메뉴바를 공유합니다. Application Kit은 자동으로 Services, Font, Windows, Help 메뉴와 같은 애플리케이션에 특정한 메뉴 일부를 자동으로 생성하고 관리합니다. Cocoa 응용프로그램이 다룰 수 있는 종류의 다른 메뉴로는 팝업 메뉴, 컨텍스츄얼 메뉴, 독 메뉴가 있습니다.
메뉴, 특히 메뉴 항목은 흥미로운 특징을 몇개 가지고 있습니다. 메뉴와 메뉴 항목은 모두 제목을 갖습니다. 메뉴의 제목은 메뉴 바에 나타나는 스트링입니다. 메뉴 항목은 추가로 타이틀 좌측에 이미지를 가질 수 있으며 타이틀 대신 이미지를 가질 수도 있습니다. 타이틀은 속성을 갖는 스트링일 수 있어서 다른 폰트와 심지어 (이미지를 메뉴 항목의 컨텐츠 영역 어디든 나타날 수 있도록 허가하는) 텍스트 부착을 허용합니다. 메뉴 항목은 단축키라 불리는 할당된 키를 가지고 (쉬프트를 제외한) 모디파이어 키와 함께 눌리면 마우스 클릭과 동일한 응답을 발생시킵니다. 메뉴 항목은 활성화 되고나 비활성화 될 수 있으며, 켜진 상태 혹은 꺼진 상태를 나타낼 수도 있습니다. 켜진 상태에는 체크 표시가 타이틀의 왼쪽에 뜨게 됩니다.
Cocoa의 메뉴와 메뉴 항목은 각각 NSMenu와 NSMenuItem 클래스의 인스턴스입니다. Cocoa 응용프로그램에서, (일반적인 의미로) 메뉴는 NSMenu 와 NSMenuItem 이 서로 보완적인 역할을 하는 단순한 디자인에 기반하고 있습니다. NSMenu 객체는 그 아래에 있는 메뉴 아이템의 콜렉션을 관리하고 그립니다. 이 콜렉션을 나타내는 NSMenuItem 객체의 배열을 포함하고 있습니다. NSMenuItem 개게는 메뉴 아이템을 특성화시키는 모든 데이터를 캡슐화시키지만, 드로잉이나 이벤트 핸들링을 직접 처리하지는 않습니다. NSMenu는 각 NSMenuItem에 있는 데이터를 사용하여 메뉴 항목을 메뉴의 경계 내에 그리고, 메뉴 내의 메뉴 항목의 위치를 찾고, 사용자가 메뉴 항목을 선택했을때, 액션 메시지를 타겟 객체에 보냅니다. 드로잉 시에, NSMenu 객체는 NSMenuItem 객체의 제목과 이미지를 사용합니다. 추적을 위해서는 항목의 인덱스를 사용합니다. 액션 메시지를 보낼 때는, NSMenuItem 객체에 저장된 액션 셀렉터와 타겟 객체를 사용합니다.
팝업 메뉴는 이 기본 메뉴 아키텍쳐를 활용합니다. 그러나, 그들은 응용프로그램의 유저 인터페이스 내에 나타나기 때문에, 추가적인 디자인을 가지고 있습니다. 사용자가 클릭하기 전에 팝업 메뉴는 버튼 비슷한 객체로 보여집니다. 이 객체는 NSPopUpButton의 인스턴스로 NSPopUpButtonCell 인스턴스를 관리합니다. (다른 말로 하면, 컨트롤-셀 아키텍쳐가 이 초기 프레젠테이션에 사용됩니다.) NSPopUpButtonCell 객체는 NSMenu 객체와 캡슐화된 NSMenuItem 객체를 포함하고 있습니다. 사용자가 팝업 버튼을 클릭하면 여기에 임베드된 메뉴가 자신을 디스플레이하도록 요청을 받습니다.
메뉴안에 있는 메뉴 항목은 현재 컨텍스트를 위해 허가될 수 있으며, 항목이 해당 컨텍스트에 상관 없다면 사용 불가 상태가 될 수 있습니다. NSMenu는 이 타당성 조사를 자동으로 수행하는 자동-활성화 기법을 포함하고 있습니다. 메뉴가 디스플레이 되기 전에, 메뉴 항목의 액션 메시지에 응답할 수 있는 객체를 리스폰더 체인에서 찾습니다. 만일 그런 객체를 찾지 못한다면, 해당 항목을 사용불가 상태로 만듭니다. 응용프로그램은 메뉴 타당성 조사를 NSMenuValidation 비공식 프로토콜을 구현하여 더욱 세련되게 만들 수 있습니다.
컨텍스츄얼 메뉴는 팝업 메뉴와 비슷하게 구현됩니다. NSMenu 객체를 (메뉴 항목 객체와 함께) NSResponder의 setMenu: 메소드를 사용하여 뷰에 붙일 수 있습니다. 이 메뉴는 뷰에 특별히 해당되는 (컨텍스트에 유효한)명령을 목록화합니다. 사용자가 뷰를 컨트롤 클릭 혹은 우클릭하면 이 메뉴가 디스플레이됩니다.
[편집] Represented Objects
셀과 메뉴 항목은 표현된 객체를 가질 수 있습니다. 액션의 타겟 메시지는 클릭된 메뉴 항목이나 셀(sender)에게 그것의 표현된 객체를 요청할 수 있습니다. 그 후 타겟은 그 표현된 객체를 디스플레이하거나, 로드하거나, 혹은 수집된 객체에 필요한 어느 작업이든 수행할 수 있습니다. 셀이나 메뉴 항목은 클라이언트가 표현된 객체에 접근하도록 하며, 아카이브하거나 복원합니다. 그러나 그 외에는 그 객체를 사용하지 않습니다.
표현된 객체를 어떻게 사용하는지 이해하기 위해 몇가지 예를 생각해봅시다. 텍스트 뷰의 배경 색을 설정하기 위한 셀들을 포함하고 있는 매트릭스가 있다고 합시다. 이들 셀의 이름은 "밝은 파랑", "밝은 회색", "분홍" 과 같은 이름을 가지고 있습니다. 각 메뉴 항목마다 표현된 객체들은 해당 색상의 RGB 컴포넌트를 캡슐화하는 NSColor 입니다. 응용프로그램은 이 표현된 객체를 Listing 6-1에서와 같이 사용할 수 있습니다.
Listing 6-1 Using a represented object
- (void)changeColor:(id)sender {
NSColor *repObj = [sender representedObject];
[textView setBackgroundColor:repObj]; // textView is an outlet
}
또 다른 예는, 셋팅의 표시되어있는 팬을 바꿀 수 있도록 해주는 Info 창의 팝업 메뉴입니다. 각 메뉴 항목의 표현된 객체로 지정된 NSView 객체들은 필요한 텍스트필드, 컨트롤 등을 포함하고 있습니다.
표현된 객체는 컨트롤이나 셀의 객체 값과 다릅니다. 객체 값이 셀이나 컨트롤이 디스플레이하는 값인 반면, 표현된 객체는 임의로 결합되어 있습니다. 예를 들어, "05/23/2006"을 디스플레이하는 텍스트 필드의 객체 값은 디스플레이된 그 값을 표현하는 NSDate나 NSCalendarDate 객체일 수 있습니다. 셀이나 컨트롤의 포매터는 객체 값을 이해할 수 있어야 합니다. (포매터는 NSFormatter 객체입니다.)
표현된 객체는 셀이나 메뉴 항목에만 사용할 수 있는 것이 아닙니다. 예를 들어, NSRulerMarker 객체도 표현된 객체를 가질 수 있습니다. 그리고 커스텀 뷰도 표현된 객체를 가질 수 있습니다.
[편집] Nib Files and Other Application Resources
지금까지 이 챕터에서는 주로 Cocoa의 기본 애플리케이션 아키텍쳐에 초점이 맞춰져서 응용프로그램의 코어 객체가 런타임시에 어떻게 함께 동작하여 이벤트처리와 드로잉을 용이하게 하는지 논의했습니다. 이제는 초점을 Cocoa 응용프로그램이 동작하는 전반적인 흐름으로 옮기도록 하겠습니다. 응용프로그램이 구동될 때, 구성하는 모든 객체를 무에서부터 만들어내는 경우는 거의 없습니다. 거의 대부분의 경우에 이들 객체들은 응용프로그램 번들에 객체 그래프의 아카이브로 저장되어 있습니다. 이들 객체 그래프는 응용프로그램의 데이터를 캡슐화하는 모델 객체를 사용자가 프로그램을 종료하기 전에 있던 상태 그대로 표현하고 있을 수 있습니다. 혹은 창, 뷰, 그리고 응용프로그램의 유저 인터페이스를 구성하는 다른 객체들의 인코딩된 표현일 수도 있습니다. 응용프로그램은 런타임시에 객체 아카이브를 로드하고 언아카이브하여 원래 객체들을 재생성합니다.
응용프로그램은 번들에 객체와 코드 뿐만 아니라 이미지와 로컬화된 스트링과 같은 다른 리소스들도 포함하고 있습니다. 또, 이 섹션에서 모든 종류의 로컬화된 응용프로그램 리소스와 로컬화 되지 않은 리소스를 찾고 로딩하는데 NSBundle 클래스의 인스턴스가 수행하는 역할을 요약하고 있습니다.
[편집] Object Archives
프로그램에서 객체는 다른 객체의 관계 네트웍 안에서 존재합니다. 객체는 특정한 객체나 객체 콜렉션을 소유할 수 있으며, 다른 객체에 의존할 수도 있고, 객체들에 메시지를 보내기 위해 객체의 레퍼런스를 가질 수도 있습니다. 이런 상호 관련된 객체들의 그물은 객체 그래프라고도 알려져 있습니다. 객체 그래프는 꽤 복잡해 질 수 있습니다.
아카이브는 객체 그래프를 저장하는 방법입니다. 보통, 파일의 형태를 취하지만 프로세스 사이에 전송되는 스트림일 수도 있습니다. 아카이브는 그래프 내의 각 객체의 아이덴티티와 그래프 내의 다른 객체들과의 관계를 보관합니다. 각 객체의 데이터와 타입을 함께 인코드합니다. 객체 그래프가 언아카이브될때, 디코드된 각 객체는 보통 원래 스트림에 인코드 되었던 객체와 동일한 클래스입니다. 객체의 인스턴스 데이터도 디코드되어 객체를 재구성하는데 사용되어집니다. 그래프 내의 객체사이의 관계 역시 복원됩니다. 그 결과로 언아카이브된 그래프는 항상 원래 객체 그래프와 거의 동일해야합니다.
아카이빙은 객체 그래프의 각 객체가 자신을 스트림에 인코드하도록 요청하는 작업입니다. 언아카이빙은 그 반대로 각 객체가 자신을 디코드하도록 요청합니다. 이 두 작업은 모두 그래프의 루트 객체에 보내지는 메시지로부터 시작됩니다. 아카이브로 저장되길 원하는 객체는 요청 받으면 자신을 반드시 인코드해야하며, 복원되기 위해서는 자신을 디코드 할 수 잇어야 합니다. 시퀀셜과 키, 이 두 종류의 Cocoa 아카이빙은 객체의 인코딩과 디코딩의 다른 스타일을 반영합니다. 시퀀셜 아카이빙은 객체가 그들의 인스턴스 데이터를 동일한 순서로 인코드하고 디코드 할 것을 요구합니다. (더 현대적인 접근법인)키 아카이빙은 저장된 인스턴스 데이터의 각 부분이 (구분할 수 있는 스트링인)키를 사용하여 저장되고 복원되도록 합니다. 인스턴스를 아카이브하길 원하는 클래스는 반드시 NSCoding 프로토콜을 준수해야합니다. 시퀀셜 아카이빙을 수행하는 객체는 NSCoder 클래스의 인코딩과 디코딩 메소드를 사용합니다. 키 아카이빙(와 언아카이빙)의 경우는, NSKeyedArchiver와 NSKeyedUnarchiver 클래스의 메소드를 사용해야만 합니다.
[편집] Nib Files
거의 모든 Cocoa 개발자들이 Interface Builder 프로그램을 사용하여 응용프로그램의 유저 인터페이스를 구축합니다. (Interface Builder를 반드시 사용해야 할 필요는 없지만, 사용하면 개발자의 작업을 훨씬 더 쉽게 해줍니다.) Figure 6-22는 Interface Builder의 일반적인 창 배열을 보여줍니다. 유저 인터페이스를 만들려면, 텍스트뷰, 버튼, 테이블 뷰와 같은 객체들을 팔렛트로부터 드래그하여 창에 드랍하면 됩니다. 그 후, 객체를 위치시키고 크기를 조정하고, 다른 속성들을 설정해줍니다. 또한, 객체 사이의 다양한 종류의 연결(아웃렛, 타겟-액션, 바인딩)도 설정해 줄 수 있습니다. Interface Builder는 커스텀 모델과 컨트롤러 클래스를 이들 클래스의 프록시 인스턴스로의/로부터의 연결을 할 목적으로 초기 정의도 하도록 해줍니다. 또한 Interface Builder를 사용하면, 커스텀 NSView 서브클래스에 할당되어 있는 플레이스홀더 객체를 지정할 수 있습니다. 유저 인터페이스를 Interface Builder 내에서



