Communicating With Objects

OSXDEV

Jump to: navigation, 찾기

Cocoa에 적용된 몇몇 디자인 패턴은 프로그램 내의 객체 간 커뮤니케이션을 돕습니다. 이들 기법과 패러다임에는 딜리게이션, 노티피케이션, 타겟-액션, 바인딩 기술이 포함됩니다. 이번 장은 이들 기법과 패러다임을 설명합니다.

목차

[편집] Communication in Object-Oriented Programs

Cocoa와 객체 지향 언어인 Objective-C 및 Java와 함께 프로그램에 특정한 행동을 추가하는 방법중의 하나는 상속을 이용하는 것입니다. 이미 존재하는 클래스의 서브클래스를 생성하여 그 클래스의 인스턴스의 행동이나 애트리뷰트를 증가시키거나 수정할 수 있습니다. 그러나 프로그램의 특성을 정하는 특별한 로직을 추가하는 방법이 이것 뿐인 것은 아닙니다. Cocoa 객체의 기능을 확장하고 재사용하는 방법이 또 있습니다.

프로그램 내 객체간의 관계는 일차원적이지 않습니다. 상속의 계층적인 구조가 있지만, 프로그램 내의 객체가 작업의 수행을 위해 런타임시에 다른 객체와 커뮤니케이션 해야만 하는 다른 객체들의 네트웍에서 동적으로 존재하기도 합니다. 오케스트라에서 연주자와 비슷하게 프로그램의 각 객체는 프로그램에 공헌하는 제한된 행동 모음인 역할을 갖습니다. 마우스 클릭에 반응하는 타원형의 면을 디스플레이 할 수도 있고, 객체의 콜렉션을 관리할 수도 있고, 창의 삶동안 발생하는 주요 이벤트를 조정하기도 합니다. 객체는 수행하도록 디자인 된 일 이상을 수행하지 않습니다. 그러나 프로그램에서 그 공헌이 수행되기 위해서는 반드시 다른 객체와 커뮤니케이션할 수 있어야 합니다. 다른 객체에 메시지를 보낼 수 있어야 하고, 다른 객체가 보내는 메시지를 받을 수 있어야 합니다.

객체가 다른 객체에 메시지를 보내기 전에, 그 객체의 레퍼런스를 가지고 있거나 사용할 수 있는 전송 기법이 있어야 합니다. Cocoa는 객체들이 서로 커뮤니케이션 할수 있는 많은 방법을 제공합니다. 이들 기법은 "Cocoa Design Patterns"에 정의된 디자인 패턴에 기반하여 강력한 응용프로그램을 효율적으로 만들수 있도록 해줍니다. 그들은 단순한 것에서부터 조금 더 상세한 것들까지 있으며 보통 서브클래싱보다 더 선호되는 방법입니다. 프로그래밍하여 구성할 수 있으며 때로는 Interface Builder에서 구성할 수 있습니다.

[편집] Outlets

객체 컴포지션은 구성하는 객체들의 레퍼런스를 얻기 위해 객체를 필요로 하는 동적 패턴으로 레퍼런스를 얻은 후 메시지를 보낼 수 있습니다. 주로 이들 다른 객체를 인스턴스 변수로 쥐고 있습니다. 이들 변수는 프로그램의 수행 중 어느 시점에는 적절한 레퍼런스로 초기화 되어야만 합니다.

아웃렛은 그런 객체 인스턴스 변수지만, 레퍼런스가 Interface Builder에서 설정되고 아카이드 된다는 점이 독특합니다. 포함하는 객체와 그 아웃렛간의 연결은 포함하는 객체가 nib 파일로부터 언아카이브 될 때마다 재설정 됩니다. 포함하는 객체는 아웃렛을 IBOutlet 타입의 인스턴스 변수로 가지고 있습니다. 예를 들어:

@interface AppController : NSObject 
{ 
IBOutlet NSArray *keywords; 
} 


아웃렛이 인스턴스 변수이기에 객체의 캡슐화된 데이터의 일부가 됩니다. 그러나 아웃렛은 단순한 인스턴스 변수가 아닙니다. 객체와 그 아웃렛과의 연결은 nib 파일에 아카이브되어, nib 파일이 로딩될 때 각각의 연결은 언아카이브되고 재설정됩니다. 그래서 다른 객체에 메시지를 보내기 위해 필요한 때에는 언제나 사용 가능합니다. 타입 지시자인 IBOutlet은 인스턴스 변수 선언에 붙는 태그로 Interface Builder가 이게 붙은 인스턴스 변수를 아웃렛으로 인식하여 디스플레이와 Xcode와의 연결을 동기화시킵니다.

아웃렛은 Interface Builder에서 연결하지만, 절차는 Xcode에서 먼저 시작됩니다. 다음 순서로 절차가 진행됩니다.

1.커스텀 클래스를 정의할 때, IBAction 지시자로 인스턴스 변수에 태그를 추가하여 아웃렛을 선언합니다.

2. Interface Builder에서 일반 객체(만일 커스텀 클래스에 이미 존재하지 않는 경우)를 nib 파일 창의 최상위 레벨로 드래그하십시오. 만일 커스텀 클래스의 인스턴스가 nib 파일의 파일 소유주(File's Owner)여야 한다면 이 단계는 필요 없습니다.

3. Interface Builder에 커스텀 클래스를 임포트합니다. 일반 객체(혹은 File's Owner)를 선택하고 Interface Builder 인스펙터의 Identify 팬의 Class 필드에 커스텀 클래스의 이름을 입력하십시오. 이렇게 해서 선택한 객체의 클래스로 커스텀 클래스를 지정하게 됩니다. 이 단계는 단 한번만 수행합니다.

4. 커스텀 인스턴스(혹은 File's Owner)를 선택합니다.

5. 이 객체에 우클릭 혹은 컨트롤 클릭하여 연결 패널을 띄웁니다.

6. Outlets에서 아웃렛을 찾고 아웃렛 옆의 원에서부터 유저 인터페이스에서 레퍼런스하길 원하는 아웃렛에 연결라인을 드래그합니다.

Figure 5-1에서 이들 단계를 완성한 후 연결 팬에 아웃렛 연결이 어떻게 보이는지를 나타냅니다. (아웃렛 옆의 원이 채워져 있고 객체의 타입이 지정되어 있는 것을 주시하십시오.)

Figure 5-1 Connecting an outlet in Interface Builder
그림:A cfg outlets1.jpg

응용프로그램은 보통 커스텀 컨트롤러 객체와 유저 인터페이스 사이의 아웃렛 커넥션을 설정하지만, Interface Builder에서 인스턴스로 표시되는 어느 객체 사이에서도 커넥션을 설정할 수 있으며 심지어 두 커스텀 객체 사이에서도 가능합니다. 어느 인스턴스 변수를 사용할때와 마찬가지로, 클래스에 포함시킬 때는 정당한 이유가 있어야 합니다. 객체에 인스턴스 변수가 더 많을 수록, 메모리를 더 많이 차지하게 됩니다. 만일 매트릭스에서 인덱스 위치를 찾거나 함수 파라미터로 포함시키거나 태그(부여된 숫자로 식별)를 사용하는 것과 같이 객체의 레퍼런스를 얻을 다른 방법이 있다면 그 방법을 사용해야 합니다.

[편집] Delegates and Data Sources

델리게이트는 다른 객체가 프로그램에서 이벤트를 만날 때 그 객체를 대신해서 혹은 동등한 입장으로 행동하는 객체입니다. 위임하는 객체는 보통 NSResponder를 상속하고 유저 이벤트에 응답하는 리스폰더 객체입니다. 델리게이트는 그 이벤트를 위한 유저 인터페이스의 위임받은 컨트롤 객체 이거나 적어도 그 이벤트를 프로그램에 특정한 방식으로 해석하도록 요청받습니다.

델리게이션의 가치는 창(NSWindow의 인스턴스)이나 테이블 뷰(NSTableView의 인스턴스)와 같은 바로 꺼내 쓸수 있는 Cocoa 객체를 고려하도록 도움을 주는데서 더 잘 알 수 있습니다. 이들 객체는 일반적인 방식으로 특정 역할을 수행하도록 디자인 되었습니다. 예를 들어 창 객체는, 창의 닫기, 크기 조절, 이동과 같은 것들을 다루고 그 컨트롤에 가해지는 마우스 작업에 응답합니다. 이런 제한적이고 일반적인 행동은 이벤트가 응용프로그램의 다른 부분에서 어떻게 영향을 미치는지, 특히 응용프로그램에 특정한 행동으로 영향을 받을때 어떤 영향을 미칠지에 대한 정보를 객체가 알 수 있는 정도에 제한을 겁니다. 델리게이션은 커스텀 객체가 애플리케이션에 특정한 행동을 꺼내쓰는 객체들에 커뮤니케이션 할 수 있는 방법을 제공합니다.

델리게이션의 프로그래밍 기법은 객체가 그들의 외양과 상태를 프로그램의 다른 부분에서 보통 사용자의 액션에 의해 발생하는 변화에 맞출 수 있도록 해줍니다. 더 중요한 것은, 델리게이션이 한 객체가 다른 객체를 상속하지 않고도 그 객체의 행동을 변화시킬 수 있도록 해준다는 것입니다. 델리게이트는 언제나 커스텀 객체중의 하나이고 정의상 일반 위임하는 객체가 스스로는 알 수 없는 응용프로그램에 특정한 로직을 통합해준다는 것입니다.

[편집] How Delegation Works

델리게이션 기법의 디자인은 단순합니다.(Figure 5-2) 델리게이션 클래스는 아웃렛을 가지고 있고, 보통 delegate라고 불리는 아웃렛을 가지고 있고 이 아웃렛을 설정하고 접근하는 메소드를 포함합니다. 또한 비공식 프로토콜을 구성하는 하나 혹은 그 이상의 메소드를 구현하지 않고 선언만 합니다. 비공식 프로토콜은 보통 딜리게이팅 클래스의 카테고리로 발생하는데 델리게이트가 프로토콜의 모든 메소드를 구현하지는 않아도 된다는 점에서 공식 프로토콜과 다릅니다. 델리게이트는 위임하는 객체와 자신을 일치시켜야하는 부분이나 객체의 기본 행동에 영향을 미치는 부분에 해당하는 비공식 메소드만 구현합니다.

Figure 5-2 The mechanism of delegation
그림:A cfg delegation1.jpg

비공식 프로토콜의 메소드들은 위임하는 객체에 의해 처리되거나 기대되는 중요한 이벤트를 표시합니다. 이 객체는 이들 이벤트를 델리게이트에 보내거나 임박한 이벤트는 델리게이트로부터 입력이나 승인을 요구합니다. 예를 들어, 사용자가 창의 닫기 버튼을 눌렀을 때, 창 객체는 windowShouldClose: 메시지를 델리게이트로 보냅니다. 이 메시지를 받은 델리게이트는 창에 관련된 데이터가 저장되어야 하는 경우, 창을 닫는 명령을 취소하거나 연기할 수 있습니다. (Figure 5-3을 보십시오.)\

'Figure 5-3 A more realistic sequence involving a delegate
그림:A cfg delegation2.jpg

위임하는 객체는 델리게이트가 메소드를 구현했을 경우에만 메시지를 보냅니다. NSObject의 메소드인 respondsToSelector:를 델리게이트에 먼저 보내어 메소드를 구현했는지를 알아봅니다. 비공식 프로토콜의 디자인에서는 이런 체크가 핵심입니다.

[편집] The Form of Delegation Messages

델리게이션 메소드는 정해진 형태가 있습니다. 그들은 위임하는 Application Kit 객체의 이름(application, window, control 등)으로 시작합니다. 이 이름은 소문자이며 "NS" 접두어가 없습니다. 보통 이들 객체는 보고된 이벤트의 임시 상태를 지시하는 조동사가 뒤에 붙습니다.(그러나 항상 그렇지는 않습니다.) 다른말로 이들 동사들은 이벤트가 막 일어나려 하는지("Should"나 "Will") 혹은 방금 막 일어났는지("Did"나 "Has")를 표시합니다. 이 임시적인 상태의 구분은 반환 값이 기대되는 메시지와 그렇지 않은 메시지로 분류하도록 도와줍니다. Listing 5-1은 리턴 값을 기대하는 소수의 Application Kit 델리게이션 메소드를 보여줍니다.

Listing 5-1 Sample delegation methods with return values

- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename; 
- (BOOL)textShouldBeginEditing:(NSText *)textObject; 
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender; 
- (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)newFrame; 


이들 메소드를 구현하는 델리게이트는 (위의 두개의 메소드에서 NO를 반환하여)임박한 이벤트를 막거나, (세번째 메소드에서 NSTerminateLater를 반환하여)연기하거나, (마지막 메소드에서 프레임 사각형(frame rectangle))제안된 파라미터를 변경할 수 있습니다.

다른 종류의 델리게이션 메소드는 리턴값을 기대하지 않는 메시지에 의해 호출되며 void를 반환하도록 되어 있습니다. 이들 메시지는 순전히 정보차원이며 메소드 이름은 보통 "Did"나 이미 발생한 이벤트를 지칭하는 단어가 들어갑니다. Listing 5-2에 이 종류의 델리게이션 메소드의 몇몇 예가 나와있습니다.

Listing 5-2 Sample delegation methods returning void

- (void) tableView:(NSTableView*)tableView mouseDownInHeaderOfTableColumn:(NSTableColumn 
*)tableColumn; 
- (void)applicationDidUnhide:(NSNotification *)notification; 
- (void)applicationWillBecomeActive:(NSNotification *)notification; 
- (void)windowDidMove:(NSNotification *)notification; 


이 마지막 메시지 그룹에서 주의해야 할 것이 몇가지 있습니다. 첫번째는 조동사 "Will"이 항상 리턴 값을 예상하는 것을 의미하지는 않는 다는 것입니다. 이 경우, 이벤트가 임박했고 막을 수 없지만 메시지는 델리게이트에게 그 이벤트를 위해 프로그램을 준비시킬 수 있는 기회를 제공합니다.

또 다른 흥미로운 점은 마지막 세개의 메소드에 있습니다. 이들 메소드의 유일한 인자는 NSNotification 객체입니다. 이것은 이들 객체가 특정 노티피케이션의 포스팅 결과로 호출된다는 것을 의미합니다. 예를 들어 windowDidMove: 메소드는 NSWindow의 NSWindowDidMoveNotification 메소드와 연관되어 있습니다. "Notifications" 섹션에서 노티피케이션을 상세히 다루고 있지만, 여기서는 노티피케이션과 델리게이션 메시지 사이의 관계를 이해하는 것이 중요합니다. 위임하는 객체는 자신이 올리는 모든 노티피케이션에 대해 델리게이트가 자동으로 옵저버가 되도록 합니다. 모든 델리게이트는 노티피케이션을 받기 위해 필요한 관련된 메소드들을 구현해야 합니다.

커스텀 클래스의 인스턴스가 Application Kit 객체의 델리게이트가 되도록 만들려면 Interface Builder에서 그 인스턴스를 delegate 아웃렛에 연결만 해주면 됩니다. 혹은, awakeFromNib: 메소드와 같이 빠른 시점에 위임하는 객체의 setDelegate: 메소드를 이용해 프로그래밍으로 설정할 수도 있습니다.

[편집] Delegation and the Application Kit

응용프로그램에서 위임하는 객체는 주로 NSApplication, NSWindow, NSView 객체입니다. 델리게이트 객체는 보통 (필수는 아닙니다만) 응용프로그램의 일부를 조작하는(조정 컨트롤러 객체인) 커스텀 객체입니다. Table 5-1에 델리게이트를 정의하는 Application Kit 클래스들이 나와 있습니다.

Table 5-1 Application Kit classes with delegates

NSApplication NSFontManager NSSplitView NSTextField
NSBrowser NSFontPanel NSTableView NSTextView
NSControl NSMatrix NSTabView NSWindow
NSDrawer NSOutlineView NSText


위임하는 객체는 그들의 델리게이트를 리테인하지 않습니다.(해서는 안됩니다.) 그러나, 위임하는 객체의 클라이언트(주로 applications)는 그들의 델리게이트가 델리게이션 메시지를 받을 수 있도록 존재하게 할 책임이 있습니다. 이것을 위해 그들은 메모리를 직접 관리하는 코드에서 델리게이트를 리테인 할 수 있습니다. 이 작업은 데이터소스, 노티피케이션 옵저버, 액션 메시지의 타겟과 마찬가지로 주의를 요합니다.

몇몇 Application Kit 클래스들은 모달 델리게이트라고 하는 더 제한된 형태의 델리게이트를 갖습니다. 이들 클래스의 객체(예를 들어 NSOpenPanel)는 모달 다이얼로그를 실행하여 사용자가 지정된 델리게이트에서 다이얼로그의 OK 버튼을 클릭하면 핸들러 메소드를 호출합니다. 모달 델리게이트는 모달 다이얼로그의 작동 범위내로 제한됩니다.

델리게이트의 존재는 다른 프로그래밍의 사용 용도도 있습니다. 예를 들어, 델리게이트를 사용하면 동일 프로그램 내의 두개의 조정 컨트롤러가 서로를 찾고 커뮤니케이션하는 것을 쉽게 할 수 있습니다. 예를 들어, 응용프로그램 전반을 컨트롤하는 객체는 다음과 비슷한 코드를 사용하여 애플리케이션의 인스펙터 창의 컨트롤러를 (현재 키 윈도라고 가정하면) 찾을 수 있습니다.

id winController = [[NSApp keyWindow] delegate];


그리고 여러분의 코드는 다음과 비슷한 것을 사용하여 글로벌 애플리케이션 인스턴스의 델리게이트인 애플리케이션-컨트롤러 객체를 찾을 수 있습니다.

id appController = [NSApp delegate];


[편집] Data Sources

데이터 소스는 유저 인터페이스의 위임된 컨트롤이 아니라 데이터의 위임된 컨트롤이라는 점만 제외하면 델리게이트와 비슷합니다. 데이터 소스는 테이블 뷰나 아웃라인 뷰와 같이 그들의 보여지는 데이터 행들을 생성해내는데 소스를 필요로하는 테이블 뷰나 아웃라인 뷰와 같이 NSView 객체에게 아웃렛으로 잡힙니다. 뷰의 데이터 소스는 보통 그 델리게이트의 역할을 수행하는 객체와 동일하지만 어느 객체든 데이터소스가 될 수 있습니다. 델리게이트에서와 마찬가지로, 데이터 소스는 뷰에 필요한 데이터를 제공해주기 위해서, 또 더 고급 구현에서는 그런 뷰에서 사용자들이 직접 수정할 수 있도록 하기 위해서 비공식 프로토콜 메소드의 하나 혹은 그 이상을 구현해야합니다.

델리게이트와 마찬가지로 데이터 소스는 데이터를 요청하는 객체로부터 받은 메시지를 받기 위해 표시되어야만 하는 객체입니다. 이들을 사용하는 응용프로그램은 메모리를 직접 관리하는 코드에서 필요하다면 리테인하여 반드시 그들의 영속성을 보장해야만 합니다.

데이터 소스는 그들이 유저 인터페이스 객체에 건네주는 객체의 영속성을 책임져야 합니다. 다른 말로 하면, 그들은 이들 객체의 메모리 관리를 책임져야 한다는 것입니다. 그러나 아웃라인 뷰나 테이블 뷰아 같은 뷰 객체가 데이터 소스로부터 데이터에 접근할 때, 데이터를 사용하는 동안 객체를 리테인합니다. 보통 디스플레이하기에 충분할 만큼만 데이터를 쥐고 있습니다.

[편집] Implementing a Delegate for a Custom Class

커스텀 클래스의 델리게이트를 구현하려면 다음 단계를 수행해야 합니다.

  • 클래스 헤더 파일에 델리게이트 액세서 메소드를 선언합니다.
- (id)delegate;
- (void)setDelegate:(id)newDelegate;


  • 액세서 메소드를 구현합니다. 메모리를 직접 관리하는 프로그램에서는 세터 메소드가 리테인 사이클을 피하고자 델리게이트를 리테인하거나 카피해서는 안됩니다.
- (id)delegate {
    return delegate;
}

- (void)setDelegate:(id)newDelegate {
    delegate = newDelegate;
}

가비지-컬렉션 환경에서는 리테인 사이클이 문제가 되지 않기 때문에, (__weak 타입 모디파이어로)델리게이트를 약한 레퍼런스로 만들어서는 안됩니다. 리테인에 대한 더많은 정보는 Memory Management Programming Guide for cocoa의 "Object Ownership and Disposal"을 보십시오. 가비지-컬렉션의 약한 레퍼런스에 대해서는 "Garbage Collection for Cocoa Essentials"를 참고하십시오.

  • 델리게이트를 위한 프로그래밍 인터페이스를 포함하는 비공식 프로토콜을 선언합니다. 비공식 프로토콜은 NSObject 클래스에 놓이는 카테고리입니다.
@interface NSObject (MyObjectDelegateMethod)
- (BOOL)operationShouldProceed;
@end


  • 델리게이션 메소드를 호출하기 전에, respondsToSelector: 메시지를 사용하여 델리게이트가 해당 메소드를 구현하였는지 확인하십시오.
- (void)someMethod {
    if ( [delegate respondsToSelector:@selector(operationShouldProceed)] ) {
        if ( [delegate operationShouldProceed] ) {
            // do something appropriate
        }
    }
}



[편집] Target-Action Mechanism

델리게이션, 바인딩, 노티피케이션이 프로그램 내 객체간 특정 형태의 커뮤니케이션을 다루는데 유용하지만, 대부분의 시각적인 커뮤니케이션에는 적합하지 않습니다. 전형적인 응용프로그램의 유저 인터페이스에는 많은 그래픽 객체로 이뤄져 있으며 가장 흔한 객체는 아마 컨트롤일 것입니다. 컨트롤은 실 생활 혹은 논리적 장치(버튼, 슬라이더, 체크박스 등)의 그래픽 유사물입니다. 라디오 튜너와 같이 실 생활의 컨트롤에서처럼 시스템(우리의 경우는 응용프로그램)에 의도를 전달하는데 사용합니다.

유저 인터페이스에서 컨트롤의 역할은 단순합니다. 사용자의 의도를 해석하여 다른 객체에게 그 요청을 수행하도록 지시합니다. 사용자가 컨트롤을 클릭하거나 리턴 키를 눌러서 하드웨어 장치가 생 이벤트를 발생시킵니다. 컨트롤은 (Cocoa에 맞게 적절히 포장된) 이 이벤트를 받고 응용프로그램에 특정한 인스트럭션으로 변환합니다. 그러나 이벤트 자체는 사용자의 의도에 대한 정보를 많이 담고 있지 못합니다. 그저 사용자가 마우스를 클릭했다거나 키를 눌렀다는 정도를 말해줄 뿐입니다. 따라서 이벤트와 인스트럭션 사이의 전환에 다른 기법이 사용되어야만 합니다.

Cocoa는 타겟-액션 기법을 사용하여 컨트롤과 다른 객체 사이의 커뮤니케이션을 합니다. 이 기법은 컨트롤과 그 셀이 응용프로그램에 특정한 인스트럭션을 적절한 객체에 보내는데 필요한 정보를 캡슐화하도록 해줍니다. 받는 객체(주로 커스텀 클래스의 인스턴스)는 타겟이라고 불립니다. 액션은 컨트롤이 타겟에 보내는 메시지 입니다. 유저 인터페이스에 관심이 있는 객체(타겟)는 인터페이스에 중요성을 알게 하고 보통 그 중요성은 액션의 이름에 반영됩니다.

[편집] Controls, Cells, and Menu Items

대부분의 컨트롤은 NSControl 클래스를 상속받은 객체들입니다. 비록 컨트롤이 타겟에 액션 메시지를 보낼 초기 책임을 지지만, 메시지를 보낼때 필요한 정보를 보내는 경우는 거의 없습니다. 이를 위해서는 주로 셀에 의존합니다.

컨트롤은 거의 항상 자신에 연관되어있는 하나 혹은 그 이상의 셀(NSCell을 상속하는 객체)을 갖습니다. 왜 이런 연관이 있을까요? 컨트롤은 NSView와 NSResponder 클래스를 포함한 그들의 조상객체의 인스턴스 변수들이 모두 결합된 채로 상속받기 때문에 상대적으로 무거운 객체입니다. 컨트롤이 비싸기 떄문에 셀은 컨트롤의 스크린 점유를 다양한 기능적 공간으로 분할하는데 사용됩니다. 셀은 개벼운 객체로 컨트롤의 전부 혹은 일부를 오버레이하는 것으로 생각할 수 있습니다. 그러나 영역의 분할 뿐 아니라 작업량도 분할합니다. 셀은 컨트롤이 해야하는 드로잉 작업의 일부를 수행하며 컨트롤이 쥐어야할 데이터의 일부를 쥐고 있습니다. 이 데이터의 두 항목은 타겟과 액션을 위한 인스턴스 변수입니다. Figure 5-4는 컨트롤-셀 기법을 묘사하고 있습니다.

추상 클래스인 NSControl과 NSCell은 둘 모두 타겟 액션 인스턴스 변수의 세팅을 불완전하게 다룹니다. NSControl은 기본값으로 관련된 셀이 존재한다면 그 정보를 지정합니다. (NSControl 자체는 자신과 셀 사이의 일대일 매핑만 지원합니다. NSMatrix와 같은 NSControl의 서브클래스는 멀티플 셀을 지원합니다.) 기본 구현에서 NSCell은 단순히 익셉션을 발생시킵니다. 타겟과 액션 세팅을 정말 구현하고 있는 클래스를 찾기 위한 상속 체인을 찾기위해서는 한단계 더 아래로 내려가 NSActionCell을 찾아야 합니다.

NSActionCell로부터 파생된 객체는 그들의 컨트롤에 타겟과 액션 값을 제공하여 컨드롤이 적절한 리시버에 액션 메시지를 작성하고 보낼 수 있도록 합니다. NSActionCell 객체는 마우스 커서의 영역을 하이라이트하고 액션 메시지를 지정한 타겟에 메시지를 보낼 때 컨트롤을 도와 마우스 커서 트랙킹을 다룹니다. 대부분의 경우에 NSControl 객체의 외양과 행동의 책임은 그에 해당하는 NSActionCell 객체에 완전히 넘겨집니다. (NSMatrix와 서브클래스인 NSForm은 NSControl의 서브클래스이지만 이 규칙을 따르지 않습니다.)

Figure 5-4 How the target–action mechanism works
그림:A cfg target action.gif

사용자가 메뉴에서 항목을 선택하면 액션이 타겟에 보내집니다. 그러나 NSMenu 객체인 메뉴들와 그들의 항목(NSMenuItem 객체)은 아키텍쳐의 면에서 컨트롤및 셀들과 완전히 분리되어 있습니다. NSMenuItem 클래스는 자신의 인스턴스를 위한 타겟-액션 기법을 구현합니다. NSMenuItem 객체는 타겟과 액션 인스턴스 변수(그리고 관련된 액세서 메소드를) 가지고 있고, 사용자가 선택하면 액션 메시지를 타겟에 보냅니다.

노트: "Controls and Menus"는 컨트롤-셀과 메뉴 아키텍쳐를 더 깊이 다루고 있습니다. 또한 Control and Cell Programming Topics for Cocoa와 Application Menu and Pop-up List Programming Topics for Cocoa도 참조하십시오.


[편집] The Target

타겟은 액션 메시지의 리시버입니다. 컨트롤 아니 주로 그 셀은 액션 메시지의 타겟을 아웃렛으로 쥐고 있습니다.("Outlets"를 보십시오.) 타겟은 보통 커스텀 클래스의 인스턴스이지만, 적절한 액션 메소드를 구현하고 있는 Cocoa 객체라면 무엇이든 될 수 있습니다.

또한 셀이나 컨트롤의 타겟 아웃렛을 nil로 설정하고 타겟 객체를 런타임 시에 결정되도록 할 수 있습니다. 타겟 객체가 nil이면 NSApplication 객체는 아래의 정해진 순서로 적절한 리시버를 찾습니다.

1. 키 윈도의 퍼스트 리스폰더로부터 시작하여 리스폰더 체인의 nextResponder 링크를 따라 NSWindow 객체의 컨텐트 뷰까지 갑니다.
{{노트상자|노트: 키 윈도는 응용프로그램에 눌린 키에 응답하며 메뉴와 다이얼로그로부터 메시지를 받는 리시버입니다. 응용프로그램의 메인 윈도우는 유저 액션의 주 포커스이며 종종 키 상태가 되기도 합니다.
2. NSWindow 객체를 시도한 후, 윈도 객체의 델리게이트를 시도합니다.

3. 메인 윈도가 키 윈도와 다를 경우, 메인 윈도의 퍼스트 리스폰더부터 다시 시작하여 메인 윈도의 리스폰더 체인을 타고 올라가 NSWindow 객체와 그 델리게이트까지 갑니다.

4. 그 다음, NSApplication 객체가 직접 응답을 시도합니다. 응답할 수 없다면, 자신의 델리게이트를 시도합니다. NSApp과 그 델리게이트가 최후의 리시버입니다.

컨트롤 객체는 그들의 타겟을 리테인하지 않고 해서는 안됩니다. 그러나, 액션 메시지를 보내는 컨트롤의 클라이언트(주로 applications)는 그들의 타겟이 액션 메시지를 받을 수 있도록 보장해야할 책임이 있습니다. 이를 위해, 메모리를 직접 관리하는 환경에서는 그들의 타겟을 리테인해야할 수 있습니다. 이런 주의는 델리게이트와 데이터 소스에 동일하게 적용됩니다.

[편집] The Action

액션은 컨트롤이 타겟에 보내는 메시지, 혹은 타겟의 입장에서는 자신이 구현하고 액션에 응답하는 메소드입니다. 컨트롤, 혹은 보통 그 셀은 액션을 SEL 타입의 인스턴스 변수로 저장합니다. SEL은 Objective-C 데이터 타입으로 메시지의 시그니쳐를 지정하는데 사용됩니다. 액션 메시지는 단순하고 분명한 시그니쳐를 가져야 합니다. 액션 메시지가 호출하는 메소드는 아무것도 반환하지 않으며 유일한 인자로 id 형태를 받습니다. 이 인자는 관례상 sender라고 불립니다. NSResponder 클래스가 정의하는 몇몇 액션 메소드의 예가 있습니다.

- (void)capitalizeWord:(id)sender;

액션 메소드는 다음의 시그니쳐를 가질 수도 있습니다.

- (IBAction) deleteRecord:(id)sender;

이 케이스에서 IBAction은 리턴 값을 지정하는 데이터 타입이 아닙니다. 어느 값도 반환되지 않습니다. IBAction은 Interface Builder가 애플리케이션 개발 단계에서 프로그래밍을 통해 추가된 액션과 자신이 갖고 있는 프로젝트의 내부 액션 메소드 리스트를 동기화시키는데 사용하는 타입 지시자 입니다.

sender 파라미터는 보통 액션 메시지를 보내는 컨트롤을 지시합니다.(물론 실제 송신자에 의해 다른 객체로 대치될 수 있습니다.) 이 개념은 엽서에 보내는 사람 주소와 비슷합니다. 타겟은 필요에 따라 sender에게 더 많은 정보를 물을 수 있습니다. 만일 실제로 보내는 객체가 다른 객체를 sender로 대체했다면, 그 객체를 동일한 방식으로 처리해야 합니다. 예를 들어, 텍스트 필드가 있고, 사용자가 텍스트를 입력하면 (아무렇게나 이름지어진) 액션 메소드인 nameEntered:가 타겟에서 호출됩니다.

- (void)nameEntered:(id) sender {
    NSString *name = [sender stringValue];
    if (![name isEqualToString:@""]) {
        NSMutableArray *names = [self nameList];
        [names addObject:name];
        [sender setStringValue:@""];
    }
}


여기 응답하는 메소드는 텍스트 필드의 컨텐츠를 추출하고, 인스턴스 변수로 캐쉬한 배열에 스트링을 추가하고, 필드를 클리어합니다. 센더에게 보낼 수 있는 다른 가능한 쿼리는 선택한 행의 NSMatrix 객체를 묻기([sender selectedRow]), NSButton 객체의 상태 묻기([sender state]), 컨트롤에 연관된 셀에게 태그 묻기 ([[sender cell] tag]), 여기서 태그는 임의의 구분자입니다.

[편집] Actions Defined by the Application Kit

Application Kit은 많은 NSActionCell을 사용하는 컨트롤을 포함하여 액션 메시지를 보내는데 사용하고 있을 뿐만 아니라 많은 클래스의 액션 메시지를 정의하고 있습니다. 이들 몇몇 액션은 Cocoa 애플리케이션 프로젝트를 생성할때 기본 타겟에 연결되어 있습니다. 예를 들어, 애플리케이션 메뉴의 Quit 명령은 글로벌 애플리케이션 객체(NSApp)의 terminate:메소드와 연결되어 있습니다.

NSResponder 클래스 또한 텍스트에 가해지는 많은 공통 작업을 위해 많은 기본 액션 메시지(스탠다드 커맨드라고 알려져 있습니다)를 정의합니다. 이를 통해 Cocoa 텍스트 시스템은 액션 메시지를 응용프로그램의 리스폰더 체인(이벤트 처리 객체의 계층적 순서)에 올려 보내어 해당 메소드를 구현하고 있는 첫 NSView, NSWindow, NSApplication 객체에 의해 처리되게 됩니다.

[편집] Setting the Target and Action

셀과 컨트롤의 타겟과 액션을 프로그래밍이나 Interface Builder를 사용하여 설정할 수 있습니다. 대다수의 개발자와 대부분의 경우에는, Interface Builder를 사용하는 것이 더 좋은 방법일 것입니다. Interface Builder를 사용하여 컨트롤과 타겟을 설정하면, 시각적 확인을 할 수 있어서 연결을 잠그고 nib 파일에 연결을 아카이브 할 수 있습니다. 이 절차는 단순하며 개발-툴 문서에 더 잘 설명되어 있습니다.

1. Xcode에서 커스텀 클래스를 정의할 때, IBAction 지시자를 사용하여 액션 메소드를 선언합니다.

2. Interface Builder에서 (커스텀 클래스를 위한 객체가 이미 없다면) 일반 객체를 드래그하여 nib 파일 창의 최고 레벨에 놓습니다.

만일 커스텀 클래스의 인스턴스가 nib 파일의 File's Owner라면 이 단계는 불필요합니다. 또, 커스텀 NSView 객체를 정의한다면 그 객체를 대신 선택하십시오.

3. 커스텀 클래스를 Interface Builder에 임포트하십시오.

일반 객체(혹은 File's Owner)를 선택하고, Interface Builder의 인스펙터의 Identify 팬의 Class 필트에 커스텀 클래스의 이름을 입력하십시오. 이렇게 하여 선택한 객체의 클래스로 커스텀 클래스를 부여합니다. 이 스텝은 한번만 수행합니다.

4. 커스텀 객체에 액션 메시지를 보내는 컨트롤이나 셀을 선택합니다.

5. 이 객체에 우클릭 혹은 컨트롤 클릭하여 연결 패널을 띄웁니다.

6. Sent Actions 에서 원하는 액션 메소드를 선택하여 액션 메소드 옆의 원에서부터 커스텀 클래스의 인스턴스를 표시하는 아이콘까지 연결 라인을 드래그합니다.

Figure 5-5에서 이 단계를 마치고 나면 연결 패널에서 액션 연결이 어떻게 보일지를 보여줍니다.

Figure 5-5  Setting target and action in Interface Builder
그림:A cfg actions 1.jpg

만일 액션이 커스텀 클래스의 슈퍼클래스나 바로 꺼내 쓰는 Application Kit 클래스에 의해 처리된다면, 액션 메소드를 선언하지 않고도 커넥션을 만들 수 있습니다. 물론, 직접 액션 메소드를 선언하면 반드시 구현해야 합니다.

액션과 타겟을 프로그래밍으로 설정한다면, 다음 메소드를 사용하여 컨트롤이나 셀 객체에 메시지를 보내십시오.

- (void)setTarget:(id)anObject;
- (void)setAction:(SEL)aSelector;



다음 예에서 이들 객체를 사용하는 법을 설명합니다.

[aCell setTarget:myController];
[aControl setAction:@selector(deleteRecord:)];
[aMenuItem setAction:@selector(showGuides:)];



타겟과 액션을 프로그래밍을 설정하면 얻을 수 있는 장점이 있으며 특정한 경우에는 이 방법만 가능할 수 있습니다. 예를 들어, 네트웍 연결이 있거나 인스펙터 창이 떠있는 경우와 같이 타겟이나 액션이 런타임 조건에 따라 변화하게 하고 싶을 수 있습니다. 다른 예는 동적으로 팝업 메뉴의 항목을 생성하고 각각의 팝업 항목이 자신의 액션을 갖게 하고 싶은 경우가 있습니다.

[편집] Bindings

바인딩은 이를 사용하여 응용프로그램 내의 디스플레이와 데이터 스토리지를 동기화 시킬 수 있는 Cocoa 기술입니다. 이들은 Cocoa 툴박스가 객체간 커뮤니케이션을 가능하게 해주는데 중요한 도구입니다. 이 기술은 MVC와 객체 모델링 디자인 패턴 둘 모두를 적용합니다. ("The Model-View-Controller Design Pattern"에서 컨트롤러 객체를 설명하며 바인딩을 소개합니다.) 이를 통해 값을 표시하는 뷰 객체의 애트리뷰트와 값을 저장하는 모델 객체 프로퍼티간에 매개 연결(바인딩)을 설정할 수 있습니다. 이 연결의 한쪽에 있는 값이 변화한다면 다른 쪽에 자동으로 반영되게 됩니다. 이 연결을 중개하는 컨트롤러 객체는 선택 관리, 플레이스홀더 값, 정리가능한 테이블을 포함하여 추가적인 지원을 제공합니다.

[편집] How Bindings Work

바인딩은 MVC와 객체 모델링 디자인 패턴에 의해 정의된 개념적인 공간에 위치합니다. MVC 응용프로그램은 객체에 일반적인 역할을 부여하며 이들 역할에 기반하여 객체를 구분합니다. 객체는 뷰 객체, 모델 객체, 컨트롤러 객체일 수 있으며 각각의 역할은 짧게 설명하면 다음과 같습니다.

  • 뷰 객체는 애플리케이션의 데이터를 디스플레이합니다.
  • 모델 객체는 애플리케이션 데이터를 캡슐화하며 데이터에 작업을 수행합니다. 이들은 주로 애플리케이션이 작동하는 동안 사용자가 생성하고 저장하는 영속 객체입니다.
  • 컨트롤러 객체는 뷰 객체와 모델 객체 사이의 데이터 교환을 중재하며, 애플리케이션을 위한 "command and control" 서비스를 수행합니다.

모든 객체, 주로 모델 객체는 프로퍼티라고 불리는 컴포넌트 혹은 특성을 정의합니다. 프로퍼티는 두가지 종류일 수 있습니다. 스트링, 스칼라, 데이터 구조와 같은 값인 애트리뷰트와 다른 객체로의 관계 입니다. 관계는 일대일 혹은 일대다 두 종류일 수 있으며 쌍방향, 재귀적일 수도 있습니다. 응용프로그램의 객체는 서로 다양한 관계를 가질 수 있으며, 이런 객체의 웹은 객체 그래프라고 불립니다. 구분할 수 이름을 가진 프로퍼티는 키 라고 불립니다. 키-패스(.로 분리된 연속된 키)를 사용하면 객체 그래프에서 관계를 돌아다니며 관련된 객체의 애트리뷰트에 접근할 수 있습니다.

바인딩 기술은 이 객체 모델을 사용하여 뷰, 모델, 컨트롤러 객체 사이의 바인딩을 설정합니다. 바인딩을 이용해, 모델 객체의 객체 그래프로부터 관계의 웹을 컨트롤러와 뷰 객체로 확장할 수 있습니다. 뷰 객체의 애트리뷰트와 모델 객체의 프로퍼티 사이에도 바인딩을 설정할 수 있습니다. (주로 컨트롤러 객체의 매개 프로퍼티를 통합니다.) 표시된 애트리뷰트 값에 변화가 생기면 자동으로 바인딩 기술을 통해 값이 저장되어 있는 프로퍼티에 전달됩니다. 또한 프로퍼티의 값이 내부에서 변화한다면 표시를 위해 뷰에게 통지됩니다.

예를 들어, Figure 5-6은 슬라이더와 텍스트 필드에 표시된 값(이들 뷰 객체의 애트리뷰트)과 모델 객체(MyObject)의 number 애트리뷰트 사이의 간단한 바인딩을 컨트롤러 객체의 content 프로퍼티를 통해 설정하는 것을 보여줍니다. 만일 사용자가 슬라이더를 움직이면 값의 변화가 number 애트리뷰트에 적용되고 그것은 다시 표시를 위해 텍스트필드에 전달됩니다.

Figure 5-6  Bindings between view, controller, and model objects
그림:A cfg sliderbindings.gif

바인딩의 구현은 키-밸류 코딩, 키-밸류 옵저빙, 키-밸류 바인딩 기법을 허용하는데 의존합니다. 이들 기법에 대한 개요와 관련된 비공식 프로토콜에 대해서는 "Key-Value Mechanisms"에서 찾을 수 있습니다. "Cocoa Design Patterns"의 옵저버 패턴에서도 키-밸류 옵저빙을 설명합니다.

어떤 객체든 두 객체 사이에 바인딩을 설정할 수 있습니다. 유일한 조건은 객체가 키-밸류 코딩과 키-밸류 옵저빙의 컨벤션에 준수하는 것 뿐입니다. 그러나 일반적으로 바인딩 설정을 매개 컨트롤러를 사용하길 원할 것입니다. 그런 컨트롤러 객체가 선택 관리, 플레이스홀더 값, 임박한 변화 수행 및 취소 기능와 같은 바인딩 관련된 서비스를 제공하기 때문입니다. 매개 컨트롤러는 NSController의 서브클래스들입니다. Interface Builder의 컨트롤러 팔렛트에서 사용 가능합니다. (아래의 "How You Establish Bindings"를 보십시오.) 또한 커스텀 매개 컨트롤러 클래스를 사용하여 더욱 특화된 행동을 얻을 수 있습니다. 매개 컨트롤러와 NSController 객체에 대한 논의는 “The Model-View-Controller Design Pattern”의 "Types of Cocoa Controller Objects"와 "Cocoa Design Pattersn"의 Mediator 패턴 섹션에 있습니다.

더 읽을 거리: 위에 요약된 디자인 패턴에 대해 더 알길 원한다면 “The Model-View-Controller Design Pattern” 와 “Object Modeling” 를 보십시오.


[편집] How You Establish Bindings

만일 응용프로그램의 유일한 커스텀 클래스가 모델 클래스라면, 바인딩을 설정하기 위한 요구조건은 이들 클래스에서 바인딩하길 원하는 프로퍼티를 위한 키-밸류 코딩 컨벤션을 준수하는 것입니다. 만일 커스텀 뷰나 커스텀 컨트롤러를 사용한다면, 이들이 키-밸류 옵저빙을 준수하는지 확인해야 합니다. 키-밸류 코딩과 키-밸류 옵저빙에 준수하기 위해 필요한 것들은 "Key-Value Mechanisms"에 요약되어 있습니다.

노트: Cocoa 프레임웍의 대부분의 클래스는 키-밸류 코딩과 (적절한 경우) 키-밸류 옵저빙을 준수합니다.


또한 바인딩을 프로그래밍을 통해 설정할 수 있지만, 대부분의 경우는 Interface Builder를 사용하여 바인딩을 설정할 것입니다. Interface Builder에서 NSController 객체를 팔렛트로부터 드래그하여 nib 파일에 드롭합니다. 그 후, Info 창의 Bindings 팬에서 원하는 뷰, 컨트롤러, 모델 객체의 프로퍼티와 바인드하길 원하는 애트리뷰트 사이의 관계를 지정해줍니다.

Figure 5-7은 바인딩의 예를 보여줍니다. 테이블 뷰의 위의 열에서 "Tax?"가 모델 애트리뷰트의 taxable과 TransactionsController(NSArrayController 객체)의 arrangedObjects 프로퍼티를 통해 바인드 되어 있는 것을 보여줍니다. TransactionsController는 모델 객체의 배열과 바인드되어 있습니다. (여기서는 보이지 않습니다.)

Figure 5-7  Establishing a binding in Interface Builder
그림:A cfg bindings in ib.jpg

더 읽을 거리: 바인딩 기술과 Interface Builder를 사용하여 바인딩을 설정하는 것에 대해 더 알고 싶다면 Cocoa Bindings Programming Topics를 읽으십시오. 이들 기법에 대한 더 완전한 설명은 Key-Value Coding Programming Guide와 Key-Value Observing Programming Guide에서 찾을 수 있습니다.


[편집] Notifications

객체 사이에 정보를 전달하는 표준적인 방법은 (한 객체가 다른 객체의 메소드를 호출하는)메시지 전달입니다. 그러나, 메시지 전달은 메시지를 보내는 객첵 받는 객체가 누구인지, 어떤 메시지에 응답하는지를 알아야 할 수 있습니다. 이런 요구 조건은 다른 종류의 메시지 뿐만 아니라 델리게이션 메시지에서도 동일합니다. 때로, 두 객체의 이런 강한 커플링이 주로 둘이 독립적인 서브시스템이어야 하는 경우에 바람직하지 않을 수 있습니다. 또한, 프로그램 내의 많은 다른 객체들 사이 하드 코드된 연결을 요구하기 때문에 비실용적이기도 합니다.

표준 메시지 전송이 적절하지 않은 경우를 위해 Cocoa는 노티피케이션이라는 브로드캐스트 모델을 제공합니다. 노티피케이션 기법을 사용하여 한 객체는 자신이 하는 일을 다른 객체들에게 알릴 수 있습니다. 이런 면에서 델리게이션과 비슷하지만 중요한 차이점이 있습니다. 델리게이션과 노티피케이션의 핵심적인 차이점은 전자는 일대일 커뮤니케이션 통로(위임하는 객체와 델리게이트 사이)인데 비해 후자는 잠재적으로 일대다 형태의 커뮤니케이션(브로드캐스트)이라는 것 입니다. 객체는 단 하나의 델리게이트만 가질 수 있지만, 노티피케이션의 수취인으로 많은 옵저버를 가질 수 있습니다. 또한 객체는 옵저버들이 어떤 객체인지 알 필요도 없습니다. 객체는 노티피케이션을 통해 간접적으로 이벤트를 관찰할 수 있으며 자신의 외양, 행동, 상태를 그 이벤트에 따라 조정할 수 있습니다. 노티피케이션은 프로그램의 조화와 일치성을 유지시키도록 하는 강력한 기법입니다.

노티피케이션 기법의 작동 방식은 개념적으로 매우 분명합니다. 프로세스는 노티피케이션의 집배 센터와 브로드캐스트 센터 역할을 하는 노티피케이션 센터라고 불리는 객체를 갖습니다. 프로그램에서 발생하는 이벤트를 알길 원하는 객체는 노티피케이션 센터에 등록하여 그 이벤트가 발생할 때 통지받도록 합니다. 이 예로, 유저가 팝업을 선택하는 것을 알고, 유저 인터페이스에 이 변화를 반영하도록 하는 컨트롤러 객체를 들 수 있습니다. 이벤트가 발생하면, 이벤트를 다루는 객체는 노티피케이션을 노티피케이션 센터에 포스트 하고, 노티피케이션 센터는 그것을 모든 객체에게 전송합니다. Figure 5-8은 이 기법을 묘사합니다.

노트: 노티피케이션 센터는 노티피케이션을 옵저버들에게 동시에 전달합니다. 포스팅하는 객체는 모든 노티피케이션이 전달되기 전까지 컨트롤을 돌려받지 못합니다. 노티피케이션을 비동기로 보내려면, 노티피케이션 큐를 사용해야 합니다.("Notification Queues"를 보십시오.) 노티피케이션 큐는 특정 노티피케이션을 지연하여 특정 범주에 따라 비슷한 노티피케이션을 합쳐서 노티피케이션 센터에 포스트합니다.


Figure 5-8  Posting and broadcasting a notification
그림:A cfg notificationcenter.gif

어떤 객체든 노티피케이션을 포스트 할 수 있으며, 어떤 객체든 옵저버로 자신을 노티피케이션 센터에 등록할 수 있습니다. 노티피케이션을 포스팅하는 객체, 노티피케이션에 포함되는 객체, 노티피케이션의 옵저버는 모두 다른 객체일 수 있으며 모두 동일한 객체일 수도 있습니다. (유휴-시간 프로세싱 같은 경우 포스팅하는 객체와 옵저빙하는 객체가 동일한 것이 쓸모가 있을 수 있습니다.) 노티피케이션을 포스트하는 객체는 옵저버에 대해 알 필요가 없습니다. 반면, 옵저버는 적어도 노티피케이션 이름과 노티피케이션 객체에 의해 캡슐화되는 딕셔너리의 키를 알아야 합니다. ("The Notification Object"에서 노티피케이션 객체가 어떻게 구성되는지를 설명합니다.)

더 읽을 거리: 노티피케이션 기법에 대한 더 상세한 설명은 Notification Programming Topics for Cocoa에서 찾을 수 있습니다.


[편집] When and How to Use Notifications

델리게이션과 마찬가지로 노티피케이션 기법도 프로그램 내의 객체사이의 커뮤니케이션을 가능하게 해주는 강력한 도구입니다. 노티피케이션은 프로그램 내의 객체가 프로그램의 다른 곳에서 발생하는 변화에 대해 알수 있도록 합니다. 일반적으로, 한 객체가 노티피케이션의 옵저버로 등록하는 이유는 특정한 이벤트가 발생하거나 발생하려 할 때 무언가 조정하길 원하기 때문입니다. 예를 들어, 커스텀 뷰가 창이 리사이즈 될 때 외관을 변화시킬 원한다면, 그 윈도 객체에 의해 포스팅되는 NSWindowDidResizeNotification 을 관찰할 수 있습니다. 또한 노티피케이션은 그 이벤트에 고나련된 데이터 딕셔너리를 포함할 수 있으므로 객체간 정보를 전달할 수 있게 해줍니다.

그러나 노티피케이션과 델리게이션 사이에 차이점이 있으며 이들 차이점은 이들 중 어느 기법이 사용되어야 하는지를 정합니다. 앞에서 언급했던 것처럼, 노티피케이션 모델과 델리게이션 모델 사이의 주요한 차이점은, 전자는 브로드캐스트 기법이고 델리게이션은 일대일 관계라는 것입니다. 각 모델은 자신의 장점을 가지고 있으며 노티피케이션은 다음의 장점들을 포함합니다.

  • 포스팅하는 객체가 옵저빙 객체의 정체를 알 필요가 없습니다.
  • Cocoa 프레임웍에 의해 선언된 노티피케이션에만 제한되지 않습니다. 어느 클래스든 그 인스턴스가 포스트 하도록 노티피케이션을 선언할 수 있습니다.
  • 노티피케이션은 애플리케이션 내 커뮤니케이션으로 제한되지 않습니다. 분산 객체를 사용하면 한 프로세스가 다른 프로세스에 대해 발생하는 이벤트에 대해 통지할 수 있습니다.

그러나 델리게이션의 일대일 모델도 장점이 있습니다. 델리게이트는 위임하는 객체에게 반환값을 주는 것으로 이벤트에 영향을 미칠 기회를 얻습니다. 반면, 노티피케이션 옵저버는 수동적인 역할만을 수행합니다. 자신과 이벤트에 읭답하는 환경에만 영향을 미칠 수 있습니다. 노티피케이션 메소드는 다음의 시그니쳐를 가져야 합니다.


- (void)notificationHandlerName:(NSNotification *);


이 조건은 관찰하는 객체가 원래 이벤트를 어떤식으로든 직접적으로 영향을 미치는 것을 방지합니다. 그러나 델리게이트는, 위임하는 객체가 이벤트를 다루는 방법에 종종 영향을 미칠 수 있습니다. 게다가, Application Kit 객체의 델리게이트는 자동적으로 그 노티피케이션의 옵저보로 등록됩니다. 그저 프레임웍 클래스가 노티피케이션을 위해 정의해놓은 노티피케이션 메소드를 구현하기만 하면 됩니다.

노티피케이션 기법은 객체 상태의 변화를 관찰하기 위한 Cocoa의 대안이긴 하지만 사실 많은 경우에는 선호되는 선택이 될 수 없습니다. Cocoa 바인딩 기술, 특히 키-밸류 옵저빙(KVO)과 키-밸류 바인딩(KVB) 프로토콜을 가능하게 하는 것이 객체가 다른 객체의 프로퍼티가 변화는 것을 관찰할 수 있도록 합니다. 바인딩 기법은 이 기능을 노티피케이션보다 더 효율적으로 수행합니다. 바인딩에서 관찰하고 관찰되는 객체의 커뮤니케이션이 직접적이고 노티피케이션 센터와 같은 중간 객체를 필요로하지 않습니다. 게다가, 바인딩 기법은 노티피케이션에서 자주 발생하는 관찰하지 않는 변화때문에 생기는 성능 페널티가 없습니다.

그러나, 바인딩보다 노티피케이션을 선호하게 되는 상황이 있을 수 있습니다. 객체의 프로퍼티의 변화가 아니라 이벤트를 관찰하길 원할 수 있습니다. 혹은, 특히 노티피케이션이 적은 수에의해 포스팅되고 관찰되는 경우처럼 KVO와 KVB 준수를 구현하는 것이 비실용적일 수 있습니다.

비록 노티피케이션의 사용이 적합한 상황일지라도, 성능에 미칠 영향에 대해 알고 있어야 합니다. 노티피케이션을 포스팅하면, 이 노티피케이션이 로컬 노티피케이션 센터로부터 관찰하는 객체에게 동시에 디스패치됩니다. 이것은 포스팅이 동기적으로 되었건 비동기적으로 되었건 상관없이 일어납니다. 만일 많은 옵저버가 있고 각각의 옵저버가 노티피케이션을 처리하는데 많은 작업을 수행한다면, 프로그램이 상당히 느려질 수 있습니다. 따라서 노티피케이션을 과용하거나 비효율적으로 사용하는 것에 주의해야 합니다. 다음 가이드라인이 이런 면에서 노티피케이션을 사용하는데 도움을 줄 것입니다.

  • 응용프로그램이 관찰할 노티피케이션을 잘 선택하십시오.
  • 노티피케이션에 등록할 때, 노티피케이션 이름과 포스팅 객체를 구체적으로 하십시오.
  • 노티피케이션을 다루는 메소드를 최대한 효율적으로 만드십시오.
  • 많은 수의 옵저버를 추가하거나 제거하지 마십시오. 몇개의 중개 옵저버가 노티피케이션의 결과를 자신이 접근할 수 있는 객체에 커뮤니케이션 하는 것이 훨씬 좋습니다.


더 읽을 거리: 노티피케이션의 효율적인 사용에 대한 상세한 설명을 Cocoa Performance Guidelines의 "Notifications"에서 찾을 수 있습니다.


[편집] The Notification Object

노티피케이션은 NSNotification의 인스턴스입니다. 이 객체는 창이 포커스를 얻거나 네트웍 연결이 끊겼다는 것 같은 이벤트에 대한 정보를 캡슐화 합니다. 이벤트가 발생하면, 이벤트를 다루는 객체는 노티피케이션 센터에 포스팅하고, 노티피케이션 센터는 등록된 모든 객체에게 즉시 브로드캐스트합니다.

NSNotification 객체는 이름, 객체, 그리고 옵션으로 딕셔너리를 포함합니다. 이름은 노티피케이션을 구분해주는 태그입니다. 객체는 노티피케이션을 포스트하는 이가 옵저버들에게 보내길 원하는 객체입니다. (주로 노티피케이션을 포스팅한 그 객체입니다.) 델리게이션 메시지에서 sender 객체와 비슷하며, 리시버가 객체에게 더 많은 정보를 물을 수 있도록 합니다. 딕셔너리는 이벤트에 관련된 정보를 저장합니다.

[편집] Notification Centers

노티피케이션 센터는 노티피케이션을 보내고 받는 것을 관리합니다. 특정 범주에 속하는 노티피케이션의 모든 옵저버에게 통지합니다. 노티피케이션 정보는 NSNotification 객체에 캡슐화됩니다. 클라이언트 객체는 자신을 노티피케이션 센터에 다른 객체가 포스팅하는 특정 노티피케이션의 옵저버로 등록합니다. 이벤트가 발생하면 객체가 노티피케이션 센터에 적절한 노티피케이션을 포스팅합니다. 노티피케이션 센터는 등록된 모든 옵저버에게 유일한 인자로 노티피케이션을 전달하며 그 메시지를 전송합니다. 포스팅하는 객체와 관찰하는 객체가 동일할 수도 있습니다.

Cocoa는 두 종류의 노티피케이션 센터를 포함합니다.

  • (NSNotificationCenter의 인스턴스인) 노티피케이션 센터는 단일 작업에서 노티피케이션을 관리합니다.
  • (NSDistributedNotificationCenter의 인스턴스인) 분산 노티피케이션 센터는 단일 컴퓨터에서 복수의 작업에 걸친 노티피케이션을 관리합니다.

다른 많은 Foundation 클래스들과 달리, NSNotificationCenter는 Core Foundation 상대(CFNotificationCenterRef)에 무료로 연결되지 않습니다.

[편집] NSNotificationCenter

각각의 작업은 기본 노티피케이션 센터를 가지고 있으며 NSNotificationCenter 클래스 메소드인 defaultCenter로 접근할 수 있습니다. 노티피케이션 센터는 단일 작업에서의 노티피케이션을 처리합니다. 동일한 기계에서의 작업간의 커뮤니케이션을 위해서는 분산 노티피케이션 센터를 사용하십시오.("NSDistributedNotificationCenter"를 보십시오.)

노티피케이션 센터는 옵저버에게 동시에 노티피케이션을 전달합니다. 다른 말로 하면, 노티피케이션을 포스팅할때, 모든 옵저버가 그 노티피케이션을 받고 처리하기 전까지 포스팅한 객체에게 컨트롤이 반환되지 않음을 의미합니다. 노티피케이션을 비동기적으로 보낼 때는 노티피케이션 큐를 사용하면 됩니다. 노티피케이션 큐는 "Notification Queues"에 설명되어 있습니다.

멀티쓰레딩 프로그램에서 노티피케이션은 언제나 노티피케이션이 포스팅된 쓰레드에서 전송되며, 그것은 옵저버가 자신을 등록한 쓰레드와 다를 수 있습니다.

[편집] NSDistributedNotificationCenter

각각의 작업은 분산된 노티피케이션 센터를 가지고 있어서 NSDistributedNotificationCenter 클래스의 defaultCenter 메소드로 접근할 수 있습니다. 이 분산된 노티피케이션 센터는 노티피케이션을 단일 시스템의 작업들 사이에 전송되어질 수 있도록 처리해줍니다. 다른 시스템 사이의 작업들 간의 커뮤니케이션을 위해서는 분산 객체를 사용하십시오.(Distributed Objects Programming Topics를 보십시오.)

분산 놑피케이션을 포스팅하는 것은 비용이 높은 작업입니다. 노티피케이션은 시스템 전반의 서버에 보내지고 그 분산 노티피케이션에 등록한 객체를 갖고 있는 모든 작업에게 배포됩니다. 노티피케이션의 포스팅과 다른 작업에 노티피케이션이 도착하는 것 사이의 격차는 제한이 없습니다. 사실, 지나치게 많은 노티피케이션이 포스팅되고 서버의 큐가 가득 차면 노티피케이션이 드롭될 수 있습니다.

분산 노티피케이션은 작업의 런룹을 통해 전달됩니다. 작업은 반드시 런룹을 NSDefaultRunLoopMode와 같은 "일반"모드 중의 하나로 돌리고 있어야 분산 노티피케이션을 받을 수 있습니다. 만일 받는 작업이 멀티쓰레드라면 노티피케이션이 메인 쓰레드에 오지 않을 수도 있습니다. 노티피케이션은 보통 메인 쓰레드의 런룹에 전달되지만, 다른 쓰레드도 노티피케이션을 받을 수 있습니다.

일반 노티피케이션 센터가 어느 객체든 노티피케이션 객체(노티피케이션에 의해 캡슐화되는 객체)가 되는 것을 허용하지만 분산 노티피케이션 센터는 NSString 객체가 노티피케이션 객체가 되도록 제한합니다. 포스팅하는 객체와 옵저버가 다른 작업에 속할 수 있기 때문에, 노티피케이션이 아무 객체의 포인터를 담고 있을 수 없습니다. 따라서, 분산 노티피케이션 센터는 노티피케이션이 스트링을 노티피케이션 객체로 사용하도록 요구합니다. 노티피케이션 매칭은 객체 포인터 대신 이 스트링에 기반하여 이루어집니다.

[편집] Notification Queues

NSNotificationQueue 객체(혹은 단순히 노티피케이션 큐)는 노티피케이션 센터(NSNotificationCenter의 인스턴스)의 버퍼 역할을 수행합니다. 노티피케이션 큐는 (NSNotification의 인스턴스인) 노티피케이션을 보통 선입선출(FIFO)의 순서로 유지합니다. 큐의 맨 앞에서 노티피케이션이 발생하면, 그것을 노티피케이션 센터로 보내고, 노티피케이션 센터는 옵저버로 등록한 모든 객체에게 그 노티피케이션을 전송합니다.

모든 쓰레드는 해당 작업을 위한 기본 노티피케이션 센터에 연관된 기본 노티피케이션 큐를 가지고 있습니다. Figure 5-9는 이 관련을 묘사하고 있습니다. 여러분이 직접 노티피케이션 큐를 생성하고 센터와 쓰레드당 복수의 큐를 가질 수도 있습니다.

Figure 5-9  A notification queue and notification center
그림:A cfg notificationqueue.gif

[편집] Coalescing Notifications

NSNotificationQueue 클래스는 Foundation Kit의 노티피케이션 기법에 두가지 중요한 기능을 기여합니다. 노티피케이션의 병합과 비동기 포스팅입니다. 병합은 방금 대기항목에 들어온 노티피케이션과 비슷한 노티피케이션을 큐에서 제거하는 절차입니다. 만일 새로운 항목이 이미 연기된 큐와 비슷하다면 새 항목은 대기항목에 들어오지 않고 관련된 모든 노티피케이션은 (큐에 있는 첫 항목을 제외하고) 모두 제거됩니다. 그러나 이런 병합의 행동에 의존해서는 안됩니다.

다음의 상수를 하나 이상 enqueueNotification:postingStyle:coalesceMask:forModes: 메소드의 세번째 인자로 지정하여 유사성의 범위를 지정합니다.

NSNotificationNoCoalescing NSNotificationCoalescingOnName NSNotificationCoalescingOnSender

비트와이즈-OR 연산을 NSNotificationCoalescingOnName과 NSNotificationCoalescingOnSender 상수에 수행하여 노티피케이션 이름과 객체 모두를 사용하여 병합할 수 있습니다. 이 경우, 큐에 더해진 노티피케이션과 동일한 이름과 sender를 갖는 모든 노티피케이션은 병합됩니다.

[편집] Asynchronously Posting Notifications

NSNotificationCenter의 postNotification: 메소드와 그 변형 메소드들로 노티피케이션 센터에 즉시 노티피케이션을 포스트할 수 있습니다. 그러나 이들 메소드의 노티피케이션은 동기화되어있습니다. 포스팅하는 객체가 수행하는 쓰레드를 재시작하기 전에 노티피케이션 센터가 모든 옵저버들에게 노티피케이션을 전송하고 리턴하기를 기다려야합니다. 그러나 NSNotificationQueue 의 enqueueNotification:postingStyle: 과 enqueueNotification:postingStyle:coalesceMask:forModes: 메소드를 사용하면 노티피케이션을 큐에 놓는 것으로 비동기적으로 노티피케이션을 포스팅할 수 있습니다.

큐에 추가하는 메소드에서 지정된 포스팅 스타일과 런룹모드에 따라 노티피케이션 큐는 비워지고 노티피케이션은 포스팅됩니다. 모드 인자는 큐가 비워질 런룹모드를 지정합니다. 예를 들어 NSModalPanelRunLoopMode를 지정하면, 노티피케이션은 런룹이 이 모드에 있을 때만 포스팅됩니다. 만일 런룹이 현재 이 모드에 있지 않으면, 노티피케이션은 다시 이 모드에 들어갈 때까지 기다립니다.

노티피케이션 큐에 포스팅하는 것은 NSPostASAP, NSPostWhenIdle, NSPostNow 이 세가지 다른 스타일중의 하나로 발생합니다. 이들 스타일은 다음 섹션에 설명되어 있습니다.

[편집] Posting As Soon As Possible

NSPostASAP 스타일로 대기된 노티피케이션 큐는 현재 런룹 모드가 요청된 모드와 일치한다는 가정하에 노티피케이션 센터에 런룹 순환이 끝나면 노티피케이션 센터에 포스팅됩니다. (만일 요청된 모드와 현재 모드가 다르다면 요청된 모드에 돌입했을때 노티피케이션이 포스팅됩니다.) 런룹은 각 순환마다 복수의 콜아웃을 할 수 있기 때문에 노티피케이션은 현재 콜아웃 끝나자마자 전달되고 런룹에 컨트롤을 반환할 수도 아닐수도 있습니다. 타이머나 소스 파이어링, 혹은 다른 비동기 노티피케이션 전달과 같은 다른 콜아웃이 먼저 수행될 수도 있습니다.

보통 NSPostASAP 포스팅 스타일을 디스플레이 서버와 같은 고비용의 리소스에 사용합니다. 다수의 클라이언트가 런룹의 콜아웃 동안 윈도 버퍼에 그린다면 각 드로우 작업 후 마다 디스플레이 버퍼에 버퍼를 플러쉬하는 것은 비용이 많이 드는 일일 것입니다. 이경우 각 draw... 메소드는 NSPostASAP 포스팅 스타일과 지정된 이름과 객체를 갖고있는 통합된 노티피케이션, 예를 들어, "FlushTheServer"와 같은 노티피케이션에 대기하게 됩니다. 그 결과로, 이들 노티피케이션의 단 하나만이 런룹의 마지막에 디스패치되고 윈도 버퍼는 단 한번만 플러쉬됩니다.

[편집] Posting When Idle

NSPostWhenIdel 스타일로 대기된 노티피케이션은 런룹이 대기 상태일 때만 포스팅됩니다. 이 경우,. 런룹의 인풋 채널에 아무것도 없고, 타이머나 다른 비동기 이벤트도 없습니다. 막 빠져나가려고하는 런룹(모든 입력 채널이 파기된 경우 발생합니다)은 대기 상태가 아니며 따라서 노티피케이션을 포스팅하지 않을 것입니다.

[편집] Posting Immediately

NSPostNow 로 대기된 노티피케이션은 노티피케이션 센터에 병합된 직후에 포스팅됩니다. 비동기적인 호출 행동이 요구되지 않을 때, 노티피케이션을 NSPostNow로 대기시킵니다(혹은 NSNotificationCenter의 postNotification: 메소드로 포스팅합니다.) 많은 프로그래밍의 경우에 동기적인 행동이 허용될 뿐만 아니라 바람직하기도 합니다. 노티피케이션 센터가 디스패칭 후 리턴하여 관찰하는 객체들이 노티피케이션을 받았고 처리도 했는지를 확실히 할 수 있습니다. 물론, 비슷한 노티피케이션이 큐에 있을때 병합하여 제거하길 원하는 경우 postNotification: 대신 enqueueNotification... 메소드를 NSPostNow와 함께 사용할 수도 있습니다.

[편집] Ownership of Delegates, Observers, and Targets

위임하는 객체들은 그들의 델리게이트나 데이터 소스를 소유하도록 되어있지 않습니다. 비슷하게, 컨트롤과 셀은 그들 자신의 타겟을 소유하지 않으며 노티피케이션 센터도 노티피케이션의 옵저버를 소유하지 않습니다. 결과적으로 메모리를 직접 관리하는 코드에서 이들 프레임웍 객체는 그들의 타겟, 옵저버, 델리게이트, 데이터 소스를 리테인하지 않습니다. 대신, 그 객체의 포인터만을 저장합니다.

노트: 메모리 관리에서 리테인되지 않은 객체 레퍼런스는 약한 연결로 알려져 있으며 이것은 가비지-컬렉션 환경의 약한 레퍼런스와 좀 다릅니다. 후자의 경우 객체의 모든 레퍼런스는 기본적으로 강하다고 여겨지며 가비지 컬렉터가 볼 수 있습니다. 약한 연결은 __weak 타입 지정자로 표시하며 보이지 않습니다. 가비지 컬렉션에서 리테인 사이클은 문제가 되지 않습니다.


메모리 관리에서 객체 소유 정책은 소유된 객체는 무조건 리테인되고 아카이브되어야 하며 레퍼런스된(그러나 소유되지 않은) 객체는 리테인되어서는 안되고 조건적으로 아카이브되어야 할 것을 추천합니다. 이 소유 정책의 실용적인 의도는 두 객체가 서로를 리테인하는 순환 레퍼런스를 방지하는 것입니다. (이것은 종종 "리테인 사이클"이라고 불립니다.) 객체를 리테인하는 것은 강한 레퍼런스이며, 객체는 그의 모든 강한 레퍼런스가 릴리즈되기 전까지 할당해제(deallocate)될 수 없습니다. 두 객체가 서로를 리테인하면 객체 사이의 연결이 깨질 수 없으므로 둘 중 어느 객체도 할당해제되지 않습니다.

만일 델리게이트, 데이터 소스, 옵저버 혹은 타겟을 가지고 있는 Cocoa 프레임웍을 서브클래싱 했다면 서브클래스에서 그 객체를 명시적으로 리테인해서는 안됩니다. 리테인되지 않은 레퍼런스를 만들어서 조건적으로 아카이브해야 합니다.

더 읽을 거리: 메모리 관리에서 소유 정책, 약한 레퍼런스, 순환 레퍼런스에 대해 더 많이 알고 싶으시다면, Memory Management Programming Guide for Cocoa에서 "Object Ownership and Disposal"를 읽으십시오. 가비지 컬렉션에서 강한 레퍼런스와 약한 레퍼런스에 대한 개요를 더 알고 싶다면 Garbage Collection Programming Guide의 “Garbage Collection for Cocoa Essentials“를 읽으십시오.



번역자 사용자:Idiel
원본문서링크 http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CommunicatingWithObjects/chapter_6_section_1.html#//apple_ref/doc/uid/TP40002974-CH7-SW15 (Last Updated - 2007-10-30)