[Objective-C] 메시지 송신 패턴
출처 : OS X 구조를 이해하면서 배우는 Objective-C Chap 15.
Notice : 정리자(돼지왕 왕돼지)가 remind 하고 싶은 내용이나 모르는 내용 기반으로 정리하는 것이기 때문에 구체적인 내용은 책을 사서 보시기를 권장드립니다.
15.1. 앱과 실행 반복
* 15.1.1. 실행 반복
-
마우스 클릭 등의 이벤트를 OS에서 받아 그에 따른 처리를 하는 루틴 실행을 반복하는 부분이 있는데, 이것을 실행 반복(run loop) 또는 이벤트반복이라고 부른다.
-
GUI 를 갖춘 Cocoa 앱에서 실행 시작 이후에는 반드시 하나의 실행 반복이 시작된다.
이것을 메인 실행 반복이라고 부른다.
동시에 앱 동작과 리소스 관리를 하는 객체가 생성되는데, 이것이 Mac OS X 에서는 NSApplication, iOS 에서는 UIApplication 이라는 클래스 인스턴스이다.
이 인스턴스가 OS 에서 전달된 이벤트에 대응하는 객체를 선택하고 적절한 메시지를 전송하는 작업을 한다.
-
카운터 관리 방식을 사용할 경우 메인 실행 반복이 메서드를 실행할 때는 자동 해제 풀이 작성되어 메서드가 종료될 때 해제된다.
가비지 컬렉션일 경우에는 하나의 이벤트 처리가 끝난 시점에서 가비지 컬렉터 기종이 요구된다.
따라서 앱에서 동작하는 메서드는 기본적으로 자동 해제풀을 자신이 관리하거나 가비지 컬렉션의 타이밍을 신경 쓰지 않아도 된다.
물론 일시적으로만 사용하는 객체가 상당히 많이 만들어지는 처리에서는 적절한 위치로 메모리를 해제하도록 하는 편이 좋을 수 있다.
또한 메인 스레드 이외의 메서드는 스스로 자동 해제 풀을 만들어야 하므로 주의해야 한다.
* 15.1.2. 타이머 객체
-
Foundation 프레임워크에는 지정한 메시지를 일정 시간 간격 후에 실행시키기 위한 NSTimer 클래스가 있다.
NSTimer 클래스 인스턴스는 타이머 객체 또는 단순히 타이머라고 부른다.
-
+(NSTimer*) scheduledTimerWithTimeInterval: (NSTimeInterval)sec
target:(id)target
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats
NSTimer 객체를 작서앟고 실행 반복에 등록해서 돌려준다.
그 후 sec 에 지정된 초만큼 지나면 타이머가 발화(실행)되어 인수 aSelector 로 지정한 셀렉터가 인수 target 객체에 송신된다.
NSTImerInterval 형은 실수이며 double 형으로 선언된다.
aSelector 에는 타이머 객체를 하나만 인수로 받는 셀렉터를 지정한다.
메시지를 송신했을 때 인수는 타이머 객체 자신이다.
메시지에 부가적인 정보를 첨부하고 싶을 때는 정보를 가진 객체를 인수 userInfo 에 지정한다.
부가 정보가 없을 때 인수 userInfo 는 nil 이 된다.
인수 repeats 가 YES 일 떄 지정한 시간 간격으로 같은 메시지가 반복해서 송신된다.
NO 일 때는 메시지를 한번만 보낸다.
-
-(void) invalidate
타이머 객체가 이 이후에는 실행되지 않도록 무효화해서 실행 반복에서 해제된다.
* 15.1.3. 메시지의 지연 실행
-
-(void)performSelector:(SEL)aSelector
withObject:(id)anArgument
afterDelay:(NSTimeInterval)aDelay
이 메시지를 송신한 후 적어도 delay 초가 지난 다음 aSelector 의 메시지를 anArgument 를 인수로 하여 리시버에 송신한다.
-
+(void)cancelPreviousPerformRequestsWithTarget:(id)ATarget
selector:(SEL)aSelector
withObject:(id)anArgument
인스턴스 메서드 performSelector:withObject:afterDelay: 를 사용해서 등록된 실행 요청이 있으면 취소한다.
이 메서드는 지정한 셀렉터와 인수 객체도 일치해야 하지만 메서드 cancelPreviousPerformRequestsWithTarget: 은 대상만 일치하면 취소할 수 있다.
-
타이머 객체를 사용할 떄와 비교하면 반복 실행을 지정할 수 없지만 대상에 넘길 인수를 지정할 수 있다는 점에서 유연성이 높다.
15.2. 델리게이트
* 15.2.1. 델리게이트 개념
* 15.2.2. Cocoa 환경의 델리게이트
-
같은 일을 상속을 사용해서 구현하면 비슷한 일을 구현할 수 있지만 실행 중에 델리게이트를 할당하거 바꿔서 사용할 수 있는 유연성이 없고 하나의 객체가 여러 객체의 델리게이트로 동작할 수도 없다.
기본 동작과 다른 기능만을 다른 클래스로 구현하는 편이 독립성 높은 소프트웨어를 작성하는 데 유리하다.
* 15.2.3. 델리게이트 설정과 프로토콜
-
프로토콜명은 그 델리게이트를 갖춘 클래스명에 Delegate 를 보통 붙인다.
-
ARC 를 사용할 떄는 약한 참조를 써서 델리게이트를 보통 정의한다.
* 15.2.4. 델리게이트를 사용한 프로그래밍
-
델리게이트 메서드를 구현하기 위한 카테고리를 만드는 것도 좋다.
15.3. 알림
* 15.3.1. 알림과 알림 센터 개념
-
프로그램에는 알림 센터(notification center) 라는 객체가 있다.
알림을 받고 싶은 객체는 미리 어떤 알림을 받고 싶은지 알림 센터에 등록해둔다.
알림은 이미 몇 가지 종류가 준비되어 있어서 각각은 알림명으로 구별된다.
자신이 새로운 알림명을 정할 수도 있다.
-
어떤 객체가 메시지 전송 요청을 알림 센터에 보낸다.
이것을 알림을 포스트(post)한다고 한다.
그러면 알림을 받도록 등록된 객체에게 메시지가 일제히 발송된다.
-
메시지를 받겠다고 알림 센터에 등록한 객체를 관찰자(observer)라고 한다.
객체는 자신을 알림 센터에 관찰자로 등록할 떄 어느 이름의 알림을 어떤 셀렉터의 메시지로 받고 싶은지 지정한다.
알림 수신 방법을 관찰자 쪽에서 정하므로 구현이 간단하다.
* 15.3.2. 알림 객체
-
NSNotification 이다.
name, object, userInfo 를 지정할 수 있다.
object 에는 일반적으로 알림을 포스트하는 객체(즉 self)를 지정하지만 다른 객체나 nil 을 지정할 수도 있다.
* 15.3.3. 알림 센터
** 기본 알림 센터
-
알림 센터는 각 프로세스마다 하나씩 미리 준비되어 있어 보통은 직접 새로 작성하지 않아도 된다.
이것을 기본 알림 센터라고 부르며 다음 클래스로 취득할 수 있다.
+(id) defalutCenter
** 알림 포스트
** 관찰자로 등록
-
모든 알림에 대해 알림을 받으려면 알림명에 nil 을 지정하면 된다.
** 관찰자 등록 삭제
-
카운터 관리 방식을 사용하는 경우에는 알림 센터는 관찰자 등록을 할 때 관찰자 및 포스트 소스로 지정된 객체 유지를 하지 않는다.
따라서 이런 객체가 해제되기 전에 관련된 설정을 알림 센터에서 확실하게 삭제해야 한다.
그렇지 않으면 해제된 객체 참조가 댕글링 포인터가 된다.
* 15.3.4. 알림 큐
-
알림을 포스트하려고 메서드를 호출하면 관련된 관찰자 모두에게 알림 메시지가 순차적으로 전달되어 처리가 끝나고 나면 포스트를 위한 메서드도 종료된다.
이러한 동작이 문제가 될 때는 알림 큐(notification queue)를 사용할 수 있다.
알림 큐는 알림 객체를 일시적으로 저장해두는 대기열로, 먼저 자장된 것이 먼저 나와서 (FIFO) 알림 센터에 포스트된다.
알림 큐는 NSNotificationQueue 라는 클래스로 제공된다.
** 비동기 포스트
-
알림을 큐에 추가한 다음 실행 반복이 지금의 실행을 마치거나 실행 반복으로 처리해야 할 입력이 없어지면 알림을 포스트한다.
이 방법을 사용하면 알림을 큐에 추가하는 메서드는 즉시 종료하고 다음 처리로 넘어갈 수 있다.
알림 포스트는 일련의 처리 후에 실행된다.
이 방법을 비동기 포스트라고 부르고, 일반적인 처리 방법을 동기화 포스트라고 부른다.
** 같은 알림 결합하기
-
알림 큐 속에 같은 알림이 있으면 하나로 합쳐서 남은 알림을 삭제한다.
15.4. 리스폰더 체인 (Responder Chain)
* 15.4.1. 리스폰더 체인 개요
-
리스폰더 체인(responder chain)이란 게층적으로 배열된 GUI 부품 사이에 메시지가 자동으로 전송되는 구조이다.
-
입력을 할 때 특정 시점에 어느 윈도우가 맨 앞에 있는지 또는 그 윈도우의 어떤 GUI 부품을 선택하는지에 따라 처리 결과가 다르다.
기본적으로 가장 전면에 선택된 GUI 부품이 처리를 시도하지만 그 부품이 메시지를 처리하지 못하더라도 그 부품을 포함한 상위 계층의 부품이 처리를 할 수도 있다.
혹은 부품을 포함한 윈도우나 그 델리게이트가 처리할 수도 있다.
-
메시지 처리 후보의 부품 객체는 개념적으로 구슬꿰기 상태가 되어 이 중에서 객체에 보내진 메시지를 처리 가능한 객체를 찾을 때까지 다음 객체, 그 다음 객체로 순서대로 돌아간다.
이 구조를 리스폰더 체인이라고 한다.
-
Mac OS X 에서 버튼이나 슬라이더, 텍스트 필드 등의 부품이나 윈도우, 패널 같은 객체는 NSResponder 클래스의 서브 클래스이다.
iOS 에서는 UIKit 프레임워크의 UIResponder 클래스의 서브 클래스이다.
-
어떤 부품 A 를 포함한 다른 부품 B 가 있을 때 리스폰더 체인은 기본적으로 A -> B 방향으로 메시지를 전달하도록 구성한다.
-
윈도우 중에서도 가장 처음에 메시지가 가는 객체를 퍼스트 리스폰더(first responder)라고 부른다.
퍼스트 리스폰더는 대부분 직전까지 마우스나 터치로 선택한 객체이다.
* 15.4.2. 앱의 리스폰더 채인
-
앱이 표시하는 윈도우는 앱의 주작업을 하기 위한 윈도우와 보조적인 기능을 제공하는 것이 있다.
주 작업을 하는 윈도우를 앱 윈도우라고 부른다.
-
파일이나 정보를 읽어서 표시하거나 편집하는 윈도우를 도큐먼트 윈도우라고 부른다.
그 외 오픈 패널, 폰트 패널, 각종 정보를 표시하는 윈도우가 있다.
-
앱 윈도우 또는 도큐먼트 윈도우 중에서 가장 앞에 표시되는 윈도우를 메인 윈도우라고 부른다.
그 외의 패널 등을 포함한 모든 윈도우 중에서 가장 전면에 있는 윈도우를 키 윈도우라고 부른다.
메인 윈도우가 키 윈도우일 때도 있다.
-
리스폰더 체인 안에서 탐색은 우선 키 윈도우의 퍼스트 리스폰더에서 시작된다.
키 윈도우에서 처리할 수 없을 경우 키 윈도우와 별도로 메인 윈도우의 리스폰더 체인도 탐색된다.
메인 윈도우에서도 처리될 수 없으면 메시지를 결국 NSApplication의 인스턴스에 전달된다.
거기에서도 처리할 수 없는 메시지를 무시되거나 경고음이 울리든지 한다.
-
iOS 의 UIKit 은 아무 버튼이나 텍스트 필드를 자동으로 퍼스트 리스폰더로 한다.
퍼스트 리스폰더에 대응할 수 없던 메시지를 쌓인 GUI 부품을 순서대로 따라가서 윈도우 및 앱(UIAplication)에 전해진다.
15.5. 메시지 전송
* 15.5.1. 메시지 전송 구조
-
리시버가 그 메시지에 대응하는 메서드를 구현하지 않았다면 런타임 시스템은 리시버에 다음 메시지를 보낸다.
-(void) forwardInvocation:(NSInvocation*)anInvocation
이 메서드는 NSObject 로 정의되어 있다.
NSObject에서 이 메서드는 다음 메서드를 호출하도록 정의된다.
-(void) doesNotRecognizeSelector:(SEL)aSelector
예외 NSInvalidArgumentException 을 발생시켜 인수의 셀렉터에 대응하는 메시지가 처리되지 않았다는 것을 알리는 에러 메시지를 생성한다.
리시버 쪽에서 forwardInvocation: 메서드를 재정의하면 실행할 수 없는 메시지가 왔을 때 다른 객체에 전송하거나 독자적인 에러 처리를 할 수 있다.
* 15.5.2. 메시지 전송에 필요한 정보
-
메시지를 전송하려면 메서드 forwardInvocation: 의 인수로 전달된 NSInvocation 객체가 지닌 정보를 이용한다.
NSInvocation 객체에는 타깃, 셀렉터, 인수 등 메시지 전송에 필요한 모든 요소가 저장된다.
* 15.5.3. 메시지 전송 정의
-
NSInvocation 정보를 사용하면 fowardInvocation: 이 메시지를 전송하도록 재정의할 수 있다.
하지만 인수 갯수가 정해진 메서드만 전송할 수 있다.
갯수가 정해져 있지 않은 인수 리스트를 받는 메서드는 전송할 수 없다.
-
런타임 시스템이 전송 받을 객체의 정보를 사용해 NSInvocation 인스턴스를 작성할 수 있도록 메서드 서명(method signature)의 객체를 돌려주는 메서드를 재정의해야 한다.
메서드 서명을 표시하는 NSMethodSignature 클래스가 있는데, 이 객체는 메서드의 인수와 반환값 정보를 기록한다.
이런 메시지 전송이나 통신 이외에는 프로그램에서 다루는 일이 거의 없다.
methodSignatureForSelector: 는 NSObject 에 정의되어 있다.
-
전송 구조를 사용해서 처리가 가능해진 메서드는 respondsToSelector: 등으로 조사할 수 없다.
전송 받을 객체가 처리할 메시지를 모두 스스로 처리하는 것처럼 보여야 할 때는 respondsToSelector: 등의 메서드도 재정의해야 한다.
* 15.5.4. 메시지 사용 금지
-
슈퍼클래스로 정의된 메서드를 서브 클래스에서 사용하면 안 될 경우에는 그 메서드를 재정의해서 사용하는 걸 방지할 수 있다.
doesNotRecognizeSelector 를 사용하면 된다.
_cmd 는 메서드의 숨은 인수로, 그 메서드의 셀렉터이다.
* 15.5.5. 프로그램 예제
15.6. 되돌리기 구조
* 15.6.1. 되돌리기 구조 개요
-
Cocoa 환경에는 했던 작업을 취소(되돌리기, undo) 하거나 재실행(되살리기, redo) 하는 전용 클래스 NSUndoManager 가 있다.
-
했던 작업을 취소하려면 동작 전에 상태를 기록해둬서 원래대로 돌리는 방법과 했던 조작과 반대의 효과를 가진 조작을 실행하는 방법이 있는데 NSUndoManager 는 후자이다.
-
되돌리기 구조는 기본적으로 앱에서 어떤 조작을 할 때 동시에 반대 작용을 하는 메시지(또는 메시지 무리)를 되돌리기 관리자에 기록한다.
되돌리기 관리자는 이런 메시지를 되돌리기용 스택에 순서대로 기록해서 되돌리기 요청이 있을 때 최신 기록 내용을 꺼내어 실행한다.
-
NSUndoManager 는 되돌리기 실행 중에 “되돌리기 관리자에 기록” 되면 그 메시지를 되살리기용 스택에 기록한다.
되살리기와 되돌리기는 반대 관계로, 되살리기 실행 중에 일어난 “되돌리기 관리자에 기록”은 되돌리기용 스택에 기록된다.
* 15.6.2. 되돌리기 관리에 기록하기
-
되돌리기 관리자에 기록하려면 두 가지 방법이 있다.
하나는 다음 메서드를 사용해서 되돌리기를 할 때 실행해야 할 메시지의 리시버와 셀렉터, 인수 객체를 기록하는 방법이다.
-(void)registerUndoWithTarget:(id)target
selector:(SEL)aSelector
object:(id)anObject
다른 하나는 다음 메서드를 사용하는 방법이다.
이 메서드의 반환값은 되돌리기 관리자 자체이다.
-(id)prepareWithInvocationTarget:(id)target
-
prepareWithInvocationTarget 은 되돌리기를 실행할 때 리시버가 무엇인지라는 정보를 받음과 동시에, 되돌리기를 실행할 동안 와야 할 메시지를 받아서 그것을 NSInvocation 객체로 통째로 저장한다.
다음과 같이 사용한다.
[[undoManager prepareWithInvocationTarget:self] decrement:n]; // decrement 가 undo 로 Invocation 형태로 저장된다.
-
되돌리기, 되살리기는 다음 메소드로 한다.
-(void)undo;
-(void)redo;
-
되돌리기/되살리기를 요구할 때 그 메시지는 리스폰더 체인 방식으로 전달된다.
되돌리기 관리자에는 그 외에도 스택을 초기화하거나 여러 메서드 호출을 하나의 되돌리기 조각으로 합쳐서 기록하는 방법 등이 있다.
자세한 건 “Undo Achitecture” 레퍼런스를 참조하라.
다음 글 : [Objective-C] 어플리케이션 구조
'프로그래밍 놀이터 > iOS' 카테고리의 다른 글
[Objective-C] 예외와 에러 (0) | 2018.01.09 |
---|---|
[Objective-C] 어플리케이션 구조 (0) | 2018.01.08 |
[Objective-C] 블록 객체 (0) | 2018.01.06 |
[Objective-C] 객체 복사와 저장 (0) | 2018.01.05 |
[Objective-C] 프로토콜 (0) | 2018.01.04 |
댓글