Wireframe

앱델리게이트 이해하기 (iOS앱 만들기 – 01)

iOS앱이 시작되는 과정

Objective-C로 앱을 제작한다면, Objective-C가 기본적으로 C의 확장형태이며 내부적으로는 C와 동일하게 작동한다는 것을 알고 있을 것이다. 따라서 Objective-C로 앱을 제작했다면 예외 없이 int main() 함수가 프로그램 전체의 진입점이자 본체가 될 것이고, 이 함수는 iOS 앱 프로젝트의 메인이 되는 main.m 파일에 정의되어 있다. 다만, 99%의 iOS앱이 앱의 론칭 방식까지 변경할 이유는 없기 때문에 우리가 주로 코드를 만지는 부분은 이 곳이 아니라, 앱의 델리게이트부터 시작한다. 하지만 프로젝트 네비게이터에서 잘 뒤져보면 어디엔가 main.m 파일은 존재할 것이다.

iOS앱은 UIApplication 클래스의 객체로 표현된다. 프로젝트의 main() 함수는 기본적으로 이 UIApplication 의 인스턴스를 만들고 GUI앱으로 작동하기 위해 필요한 런루프를 시작하는 작업을 수행한다. 앱 객체가 생성되어 로드된 이후부터는 모든 앱의 흐름에 관한 처리는 이 앱 객체가 처리하게 된다.

iOS앱을 시작할 때 내부적으로 일어나는 일은 다음과 같다.

  1. main() 함수가 시작된다.
  2. main() 함수 내부에서는 UIApplicationMain() 함수가 호출된다.
  3. UIApplicationMain() 함수는 내부에서 UIApplication 객체 인스턴스를 만든다.
  4. 앱 객체를 생성한 직후에는 Info.plist 파일을 찾아서 읽어들이고, 이 파일의 내용에 기반하여 메인 nib 파일 등 필요한 파일을 읽거들인다.
  5. 메인 nib 파일이 없거나, 혹은 읽어들인 nib 파일 내에 앱 델리게이트가 존재하지 않는다면, 앱 델리게이트의 인스턴스를 생성한다. 이 때 사용하는 클래스는 UIApplicationMain() 함수가 호출될 때 인자로 전달된다.
  6. 기본적인 구성이 완료되면 런루프를 시작한다.
  7. 모든 준비가 완료되면 앱 델리게이트 객체에게 -application:didFinishedLaunchingWithOptions: 메시지를 보낸다.

UIApplicationMain 함수

이러한 앱의 시작 절차는 UIKit에 의해 명시되며, Xcode 프로젝트는 기본적으로 이러한 절차를 통해 앱이 초기화되도록 프로젝트 템플릿을 구성한다. 사실 “만약 Objective-C가 C라면 main() 함수부터 시작하게 되는데, 왜 Xcode에서는 application:didFinishLaunchingWithOptions: 부터 시작하는 것이지?”라는 이상한 질문을 떠올리는 초보 개발자가 아닌 이상, 이 부분은 그 누구도 신경쓰지 않을 문제인지도 모르겠다. 다만 이런 과정에 대해 궁금증을 가지고 파들어가 보는 것은 iOS앱이 어떤 식으로 시작되고 로드되는지, 코드와 상관없어 보이는 nib 파일은 어떤식으로 마법처럼 앱의 화면으로 만들어져서 나타나는지 등에 대한 궁금증을 풀어갈 열쇠가 될 수 있다. 또 가끔 인터페이스 빌더를 사용하지 않고 모든 UI를 코드로만 구성하려고 할 때에는 main.m 파일을 편집해야 하기에, 이런 지식은 필요할 것이다.

macOS용 앱이나 iOS앱은 GUI기반의 앱이며, 이러한 앱들은 그간 간단하게 연습삼아 만들어봤던 명령줄 프로그램과는 작동하는 방식이 다르다. 프로그램은 실행된 직후 초기화와 관련된 몇 가지 구성을 제외하면 아무 일도 하지 않으며, 사용자가 종료시키지 않는 이상 자동으로 끝나지도 않는다. 즉 main() 함수가 호출되고 몇 줄의 코드가 작동한 뒤 0을 리턴하는 게 아니란 말이다. (사실은 맞지만…?)

실제로 main.m 파일을 찾아서 main() 함수를 찾아보면 여느 C언어 코드의 그것과 똑같이 생겼으며, 다른 C함수를 하나 호출하는 것 이상의 일은 하지 않는 것을 볼 수 있다. 그 함수는 UIApplicationMain 이라는 함수이며, 그 원형은 다음과 같다.

int UIAppicationMain(
    int argc,
    char *argv[],
    NSString * principalClassName,
    NSString * appDelegateClassName
);

이 함수는 UIKit에서 앱의 라이프사이클을 시작하는 함수이다. 앞서도 간단히 언급했듯이 이 함수는 UIApplication 인스턴스를 만들고, 이 객체가 앱으로서 기능하기 위한 제반 준비를 수행한다. 코코아터치 프레임워크에서 이 과정은 “앱 로딩 프로세스”라 불리며, 앱 구동에 필요한 앱 델리게이트, 윈도, 뷰, 뷰 컨트롤러등의 객체들을 로딩하고 초기화하며, 이들을 연결하여 GUI를 구성하는 등의 작업을 수행한다. 흥미로운 점은 이 과정에서 프로그래머가 개입하여 일일이 설정하거나 세팅할 필요가 없게끔 되어 있다는 점이다. Info.plist 라는 프로퍼티 리스트 파일을 읽도록 약속되어 있고, 이 파일은 XCode 프로젝트에서 앱에 대해 설정한 내용들이 들어있다.

UIApplicationMain() 함수는 main() 함수의 두 인자를 그대로 물려 받으면서, 두 개의 인자를 추가로 정의하고 있다. 먼저 principalClassName 은 앱 객체가 될 클래스의 이름으로, 아주아주 특별한 경우를 제외하면 @"UIAppication" 이 될 것이다. 그리고 appDelegateClassName 은 델리게이트 클래스의 이름이다. 이 이름은 메인 nib 파일에 앱 델리게이트 객체가 만들어져 있다면 사용되지 않는다. 예외적인 어떤 상황에서는 메인 nib 파일이 없거나, nib 파일 내에 앱 델리게이트 객체의 정보가 없다면 이 클래스 이름을 통해 클래스를 찾아서 인스턴스로 만들고, 생성한 앱 객체의 델리게이트로 연결해주게 된다.

따라서 메인 nib 파일을 사용하지 않는 프로젝트를 만든다면, 이 과정을 정상적으로 수행하게 하기 위해서 main.m 파일을 수정해서 UIApplicationMain() 함수에 올바른 인자를 전달하도록 해야할 것이다.

#import <UIKit/UIKit.h>
#import "MYAppDelegate.h"

int main(int argc, const char ** argv)
{
  @autoreleasepool {
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([MYAppDelegate class]));
  }
}

메인 nib 파일은 대부분의 프로젝트에서 주로 MainWindow.xib 으로 되어 있다. (.xib 이라는 확장자는 .nib 파일을 xml 비슷한 형태로 직렬화한 것인데, 관례적으로 ‘닙’이라고 계속 읽는다.) 파일의 이름은 고정되지 않고 Info.plist 파일에 명시되어 있고, UIApplicationMain()은 그 정보를 사용하게 된다.

기본 프로퍼티 리스트 파일은 실제로는 앱/번들이름-Info.plist 라는 파일로 번들에 포함되는데, 실제 실행되는 앱의 이름 정보를 이용해서 파일을 찾는다. 여기에는 Xcode에서 설정가능한 거의 대부분의 정보가 담겨있다. 이 속에 메인 nib 파일의 경로가 들어있고, 그 nib 파일을 로드했다면, 역직렬화를 통해서 앱 델리게이트, 윈도, 루트 뷰, 뷰 컨트롤러 등등의 상태를 복원해낸다.

앱 델리게이트의 역할

앱 객체(UIApplication)는 앱의 라이프사이클에 따라서 발생하는 이벤트에 따라 다양한 메시지를 수신하게 된다. 이러한 메시지에 반응하기 위해서는 앱 객체를 매번 커스터마이징해야 하는데, 그렇게 하기 보다는 별도의 커스텀 클래스 객체에게 델리게이트 패턴을 사용해서 그러한 메시지의 처리를 위임한다. 이러한 델리게이트 패턴은 클래스 기반 OOP에서 클래스 상속을 피할 수 있는 방법으로, UIKit 전반에 굉장히 자주 쓰이는 패턴이다.

애플은 이를 위해서 Objective-C 기능 중에서 카테고리나 프로토콜과 같은 기능을 공을 들여서 발전시켜왔고, 이러한 기조는 후에 Swift의 언어 디자인에서도 매우 중요한 비중을 차지하게 된다. 특히 강한 타입 언어인 Swift를 유연하게 사용하기 위해서는 프로토콜과 같은 기능은 잘 이해할 필요가 있다.

앱의 시작과 종료

앱이 시작되면 앱 델리게이트는 application:didFinishLaunchingWithOptions:라는 메시지를 받게 된다. 이 메소드는 앱이 실행 준비를 거의 마쳤을 때 호출되는데, 이 때 앱에 필요한 크리티컬한 데이터의 초기화를 하면 된다. 단, 아직 앱이 화면에 나타나기 이전이므로 시간이 많이 걸리는 작업을 여기서 하면 안된다. 데이터의 초기화를 위해 이 메소드는 거의 모든 경우에 오버라이드 된다.

- (BOOL)application:(UIAppication *)application
  didFinishLaunchingWithOptions:(NSDictionary *)lauchOptions

만약 nib 파일을 사용하지 않는 앱이라면, UI를 그리는데 필수적인 윈도 및 메인 뷰가 없는 상태이므로 이 시점에서 programatic한 방법으로 생성해서 설치해야 한다.

앱을 종료하려 할 때에는 앱 델리게이트는 아래의 메시지를 받게 된다. 앱이 종료되기 전에 잃어버려서는 안되는 사용자 데이터가 있다면 보통 이 시점에 저장하면 된다.

- (void)applicationWillTerminate:(UIApplication *)application

그 외에도 앱 델리게이트는 앱의 상태 변화에 대해 감지할 수 있다. 비활성화 된다거나 잠자기에 들어간다거나 저장된 상태로부터 복구된다거나 하는 등 앱의 상태가 바뀌게 되면 앱 객체로부터 해당 이벤트에서 호출될 델리게이트 메소드들을 호출받게 된다. 앱델리게이트의 주요 메소드에 대해서는 UIAppDelegateProtocol 공식 문서를 참고하면 되고, 여기에는 앱의 각종 상태변화등을 감지하게 되는 시점에 대해 알게 된다.

Exit mobile version