Mac App’s Core Objects

App Life Cycle

여느 C 프로그램과 같이1 OSX앱도 main 함수로부터 시작한다. 이 함수는 단순히 NSApplicationMain() 함수를 다시 호출한다. 대략 이렇게 생겼다.

#import <Cocoa/Cocoa.h>

int main(int argc, const char* argv[]) {
    return NSApplicationMain(argc, (const char**) argv);
}

NSApplicationMain 함수는 앱을 초기화하고 실행할 준비를 한다. 초기화 과정의 일환으로 이 함수는 아래와 같은 작업을 한다.

  1. NSApplication 클래스 객체를 하나 만든다. 이 객체는 +sharedApplication 메소드로 얻을 수 있다.
  2. Info.plist 파일을 읽어들여서 여기에 기록된 NSMainNibFile로부터 nib 파일을 읽어들인다. 만약 스토리보드를 쓰고 있다면 기본 스토리보드를 읽어와서 객체들을 구성한다. UIApplicationMain과는 달리 앱 델리게이트는 기본 Nib 파일에 반드시 포함되어 있어야 한다.
  3. 애플리케이션 객체의 run 메소드를 호출한다. 이로써 론칭과정은 끝나고 앱의 이벤트 처리를 시작한다.

앱 객체가 run 메시지를 받으면, 델리게이트에게 앱이 런칭될 것을 알려주고, 메뉴바를 표시한며, 앱 호출시 전달받은 인자를 처리해서 열어야 하는 파일을 찾아 열어준다. 이 모든 일중에서 파일을 읽어오는 것을 제외한 모든 것은 메인스레드에서 이루어지며, 파일 로딩은 NSDocument 클래스의 +canConcurrentlyReadDocumentsOfType: 메소드가 YES를 리턴하는 경우, 백그라운드에서 이루어진다.

만약 이전 실행때의 창의 위치라든지 이런 정보를 저장하도록 했다면, 코코아는 자동으로 이 정보를 찾아와서 창을 재구성한다.

이벤트 루프

앱이 생성되면, 메인 루프 프로세스가 들어온 이벤트들에 따라서 작절한 객체로부터 핸들러를 디스패치하는 작업을 시작한다. 먼저 NSApplication 객체가 만들어지면, 이는 시스템의 창서버와 연결하여 하드웨어 혹은 외부 입력으로 인한 여러 이벤트를 앱이 받을 수 있도록 한다. 또한 앱 객체는 FIFO 이벤트 큐를 만들어서 여기에 이벤트를 저장한다. 메인 이벤트루프는 이 이벤트 큐에 들어온 이벤트들을 처리하게 된다.

먼저 윈도 업데이트(MS에서 하는 그거 말고) 알림을 받으면, dirty로 표시된 창을 다시 그린다. 그리고 이벤트 큐로부터 이벤트를 하나씩 꺼내어(nextEventMatchingMask:untileDate:inMode:dequeue:) 이를 이벤트 데이터로 변환하고 NSEvent 객체를 만든다. 그리고 -sendEvent: 메소드를 통해 적절한 처리를 할 수 있는 객체에게 보낸다.

대표적인 이벤트로 키 이벤트와 마우스 이벤트가 있다. 키 이벤트를 키보드를 눌러서 발생한 이벤트로, 현재 키 윈도우(활성화된 윈도)가 받아서 처리한다. 마우스 이벤트를 마우스 동작이 일어난 위치의 윈도가 이를 받아서 처리한다. AppKit에서 뷰는 그 스스로가 응답체인에 있기 때문에 윈도 객체는 이벤트가 발생한 뷰에 다시 이벤트를 돌려주어 처리하도록 한다.

뷰 -(이벤트발생!)--> 시스템윈도서버 --> 앱 --> 윈도 --> 뷰 --> 부모 뷰 --> 루트 뷰 --> 그외 응답체인

종료

OSX 10.7 이후부터, Quit 명령을 써서 앱을 종료하는 것은 지양되게끔 하고 있다. 대신 두 가지 정책이 권장되는데,

  1. 자동 종료는 사용자가 앱을 끌 필요를 없게 만든다. 대신 시스템이 앱의 종료 시점을 결정하며, 이는 메모리가 부족한 경우 자원을 회수하기 위해 동작한다.
  2. 급종료는 시스템이 앱의 최종동작을 기다리지 않고 앱 프로세스를 즉각 중단시키는 것을 말한다. 이는 로그아웃, 재시작, 시스템 종료를 더욱 빠르게 할 수 있기 위해 사용된다.

자동 종료는 앱의 종료등의 관리를 사용자로부터 시스템으로 이전한다. 사용자들은 단지 앱을 실행시키고 필요한 경우에 가용한 앱을 쓰면 되는 것 뿐이다. (대신에 이를 위해서는 앱이 몇가지를 지원해야 한다.)

권장 런타임 동작

자동 종료

  • Info.plist 의 키를 쓰거나 프로그램적 방법으로 자동 종료를 지원한다고 명시한다.
    • NSSupportsAutomaticTermination 키가 있어야한다.
    • NSProcessInfo 클래스를 이용하여 동적으로 선언하기도 한다.
  • 창 정보를 저장/복원할 수 있어야 한다.
  • 사용자의 데이터는 적절한 임의의 시점에 저장되어야 한다. 슈 박스 앱들은 적절한 체크포인트마다 데이터를 저장해야 하며, 문서 기반 앱들은 NSDocument 의 자동 저장 기능을 활용한다.
  • 가능하면 긴급종료도 지원한다.

자동저장

자동 종료를 지원하려면 자동저장이 가능해야 한다. 사용자에게 데이터를 저장할 것을 강요하는 것을 피하고 대신에 앱이 적절한 시점에 데이터를 저장한다. NSDocument를 이용하는 경우 autosavesInPlaceYES로 리턴하도록 하기만 하면 간단히 처리된다.

싱글 윈도앱에서는 언제 디스크에 데이터를 기록할지를 정래햐 한다. 주로 다음과 같은 시점이 좋은 체크포인트가 된다.

  • 사용자가 창을 닫거나 앱을 종료할 때
  • 앱이 비활성화될 때
  • 앱이 숨겨질 때
  • 데이터에 유효한 변화가 발생할 때

즉 언제든 데이터를 저장해도 좋다. 매 레코드가 편집될 때마다 저장할 수도 있고, 특정 ㅅ시점까지 기다렸다가 저장할 수도 있다. 이는 데이터가 항상 최신의 버전으로 저장된다는 장점은 있지만, 이를 위한 미세한 컨트롤은 필요하다. (불완전한 데이터가 저장된다거나 하는 일이 없도록) 이런 변경을 쉽게 처리하는 대안으로는 코어데이터가 있다.

긴급종료

긴급종료를 지원하기 위해서는 데이터가 안전하게 저장된다는 보장을 하기 위해서 앱은 데이터를 좀 더 전향적으로 저장해둘 필요가 있다.

UI 보전

라이온에서부터 지원된 복구 기능은 앱을 종료한 후 재실행했을 때 마지막 상태를 복원해주는 기능을 포함한다. 이를 위해서는 앱의 상태를 저장해두었다가, 앱 시작시 이전 상태를 읽고 이를 복원하는 것이 필요하다.

— 별도 추가

앱은 여러 많은 조각으로 이루어진다.

코코아 앱은 코코아터치 앱보다도 훨씬 더 많은 조각으로 이루어질 수 있다. 여기에는 메뉴바, 하나 이상의 윈도, 그리고 각 윈도마다 하나 이상의 뷰가 들어간다. 메뉴 바는 앱에 내릴 수 있는 명령을 모아두는 보관소 같은 것이다. 이 명령들은 앱 전체 혹은 활성화된 창이나 선택된 객체에 대해 한정해서 수행될 수 있다. 이러한 ㅁ명령들을 정의하는 것은 개발자의 몫이다.

앱의 데이터 모델과 UI를 표현하기 위해서 창과 뷰를 쓸 수 있다. ‘창’은 NSWindow의 인스턴스이고, 패널은 NSPanel의 인스턴이다. (이는 NSWindow의 자손이다.) 단일 창 앱의 경우에도 하나의 메인 윈도와 부가적인 윈도를 가질 숭 씨다. 멀티 윈도 앱은 각각의 복수 컨텐츠를 관리하는 창이 여러 개 있다.

뷰는 NSView의 인스턴스인데, 창 내의 사각형의 영역에 컨텐츠를 그리는 일을 담당한다. 뷰는 컨텐츠를 표시하고 사용자와 상호작용하는 일차적인 매커니즘을 맡는다.

  • 컨텐츠를 그리고 애니메이트 한다.
  • 서브뷰를 배치하고 관리한다.
  • 이벤트를 핸들링한다. 경우에 따라 이벤트는 다른 객체로 포워딩된다.

이벤트 핸들링

시스템의 윈도서버는 마우스와 키보드를 추적하고 여기서 발생하는 이벤트를 앱으로 전달한다. 앱으로 전달된 이벤트는 이벤트 큐에 에빈트 객체로 변환되어 저장되는데, 이는 앱 객체에 의해 적절한 담당객체로 넘어가고 담당객체는 이벤트 핸들러를 호출하게 된다.

마우스,키보드 조작 --> OS --> 이벤트 큐 --> 앱 객체 --> 다른 코어 객체 --> OS --> 화면으로 피드백

런루프는 특정한 실행 스레드 위에서 입력 소스를 모니터한다. 앱의 이벤트 큐는 이러한 입력 소스 중 하나를 표현한다. 이벤트 큐가 비어 있으면 메인스레드는 잠들어 있게 되고, 이벤트가 도착하면 이벤트 큐는 스레드를 깨우고 핸들러를 디스패치하도록 NSApplication 객체에 이를 전달한다. 처리가 끝나면 컨트롤은 다시 런루프로 돌아가서 다음 이벤트를 처리하거나, 다른 입력 소스에 이벤트가 있는지 확인하거나 메인 스레드를 다시 재우게 된다.

이벤트를 분산하고 처리하는 것은 응답 객체가 하는 일이다. 응답 객체는 NSResponder 클래스의 인스턴스로 NSApplication, NSWindow, NSDrawer, NSView, NSWindowController, NSViewController 등이 있다. 이벤트 큐로부터 이벤트가 뽑아져 나오면, 앱은 윈도 객체로 이를 보낸다. 윈도 객체는 다시 first responder인 객체로 이벤트를 전달한다. 마우스 이벤트의 경우에 이는 주로 액션이 일어난 뷰 객체가 된다.

만약 첫번째 응답 객체가 이를 처리하지 못하면 이벤트는 다음번 응답 객체로 넘어간다. 이는 보통 상위 뷰나 뷰 컨트롤러, 윈도 컨트롤러가 된다. 이러한 응답 객체의 연결은 흔히 응답체인(reponder chain)이라 불린다. 이벤트가 처리될 때까지 응답 체인 내에서 메시지는 계속 전달된다. 최상위 객체까지 거슬러올라가도 처리되지 못하는 이벤트는 무시된다.

응답객체는 일련의 프로그램적인 액션을 통해서 이벤트를 처리한다. 예를 들어 컨트롤 객체(NSControl)는 다른 객체로 메시지를 보내는 것으로 이벤트를 처리하는데, 이는 보통 특정한 뷰나 윈도 컨트롤러가 된다. 컨트롤러는 이 메시지를 처리하면서 피드백을 위해 뷰를 변경하게 된다. 뷰를 변경하는 과정은 효율적인 처리를 위해 시스템의 그래픽 인프라스트럭쳐로 제어를 넘겨주게 된다.

그래픽과 프린팅

맥 앱이 컨텐츠를 그리는 방식에는 다음 두 가지 방법이 있다.

  1. 네이티브 그래픽 기술(Core Graphics와 AppKit)
  2. Open GL

네이티브 그래픽 처리 기술은 코코아 뷰나 윈도에 의해서 제공되며 현재 커스텀 컨텐츠를 그리게 된다. 뷰가 처음 나타나게 되면 뷰는 그 컨텐츠를 그리도록 요청받는다. 시스템 뷰는 보통 자신이 알아서 이를 수행하며, 커스텀 뷰의 경우에는 drawRect:를 반드시 구현해야 한다. 이 메소드 내에서 여러분은 컨텐츠를 시각적으로 그리기 위해 네이티브 기술을 이용한다. 만약 수동으로 뷰의 컨텐츠를 업데이트하고자 한다면 drawRect:를 직접 호출하는 것이 아니라 해당 뷰에게 setNeedsDisplay:, setNeedsDisplay:inRect:를 호출하면 시스템이 적절한 시점에 drawRect:를 호출하게 된다.

만약 OpenGL을 이용한다면 동일하게 창이나 뷰를 만들 수 있는데, 대신 뷰는 OpenGL 컨텍스트를 그리기 위한 렌더링 공간을 준비하게 된다. 이 경우에는 직접 앱이 업데이트를 하는 작업을 맡아야 한다.

텍스트 핸들링

코코아의 텍스트 시스템은 표시해야하는 텍스트를 그리는 처리를 위임받는다. 코코아는 고품질 텍스트 서비스를 AppKit내의 텍스트와 관련있는 클래스들을 이용하여 제공하게 되며, 이를 통해 텍스트를 생성하고, 편집하고, 화면에 표시하며, 이를 저장할 수도 있다.

코코아 텍스트 시스템은 기본적이며 진보된 텍스트 처리 기능을 제공한다. 동시에 전세계 모든 국가의 언어에서 사용되는 문자를 지원하고, 문자의 방향 및 레이아웃 기능들을 제공한다. 그 외에도 자간, 장평, 개행, 정렬 등의 처리도 제공한다.

이러한 코코아 텍스트 시스템은 코어 텍스트라는 로우 레벨 텍스트 처리 엔진을 기반으로 한다. 코어 텍스트는 코코아 텍스트 기술의 대부분을 구현해 놓고 있는데, 개발자는 코어 텍스트를 직접 다룰 일이 많ㅇ지 않다. 대신 코어 텍스트는 API를 제공하고 있어서 자체 레이아웃 엔진을 사용하거나 하는 일에서 제어할 수 있는 여지를 준다.

메뉴 바 구현하기

NSMenu, NSMenuItem은 모든 타입의 메뉴를 위한 클래스이다. NSMenu의 인스턴스는 메뉴 아이템 집합을 관리하고, 이를 화면에 그리는 일을 한다. 메뉴의 상호작용은 보통 자체적으로 이루어지며, 메뉴를 선택했을 때 주어지는 동작은 앱이 처리한다.

메뉴 아이템의 액션은 두 가지 방식으로 처리될 수 있는데, 하나는 First Reponsder가 이 메시지를 받아서 현재 포커스 되어 있거나 선택되어 있는 객체가 이를 처리하게 할 수 있고, 다른 한 가지 방법으로는 앱 델리게이트 객체가 이 액션을 받아서 처리할 수 있다.


  1. C프로그램이 맞음.