[iOS 앱 만들기 007] iOS 앱의 최소 단위

지금까지 iOS앱을 구성하는 화면이 어떻게 구성되고, 또 그 화면을 구성하는 요소들이 기본적으로 어떤 특징을 가지고 있는지 알아보았다. 이번에는 iOS앱을 구성하는 화면외의 구성요소들을 살펴보도록 하자.

iOS앱의 구성요소

앱을 구성하는 요소는 다음과 같다.

  • 애플리케이션 객체 (UIApplication)
  • 윈도 객체 (UIWindow)
  • 앱 델리게이트 (커스텀 클래스, UIResponder를 서브 클래싱한다.)
  • 루트 뷰 컨트롤러 (UIViewController의 서브 클래스)

이 들 객체가 있으면 앱은 실행되고 첫 화면을 표시할 수 있게 된다.

애플리케이션 객체

애플리케이션 객체는 앱의 동작의 중심이 되는 객체이다. 자세한 설명은 개발자문서를 참고한다. 앱 객체는 메인 이벤트 루프를 생성하고 앱 외부에서 벌어지는 일들과 앱 내부에서 처리할 수 있는 동작을 연결하는 기능을 한다.

UIApplication 클래스를 서브클래싱하여 커스텀 앱 객체를 만드는 것도 가능하지만, 통상 앱 객체가 하는 많은 일들에 대한 효과는 앱 델리게이트로 위임되어 처리되기 때문에 커스터마이징할 필요는 거의 없다.

앱 델리게이트 객체

앱 객체 자체에는 거의 직접 접근할 일이 없는데, 대부분의 작업이 앱 델리게이트로 위임되기 때문이다. 앱 로딩시에 화면을 표시하는 일이라든지, 앱이 종료되기 직전에 상태를 저장하거나 변경사항을 저장하는 일, 그외에 앱의 라이프 사이클에서 받게 되는 많은 통지를 처리를 하는 일을 앱 델리게이트가 하게 된다.

앱 델리게이트 객체는 표준 템플릿에서는 UIResponder 클래스를 상속받고 있지만, 원칙적으로는 NSObject를 상속받는 일반 객체여도 상관은 없다. 대신에 이 클래스는 UIAppliactionDelegate 프로토콜을 따라야 한다.

앱이 시작되면 앱 객체가 생성되고 앱 객체는 application:didFinishLauchingWithOptions:를 호출하게 된다. 앱의 초기화는 이 함수에서 정의하면 된다. 만약 nib 파일이 없다면 다음과 같은 식으로 코드가 구성될 것이다.

-(BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions  
{  
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];  
    _rootViewController = /* make root view controller */;  
    [self.window setRootViewController:_rootViewController];  
    [self.window makeKeyAndVisible]; // 윈도를 화면에 표시함

    return YES; // 앱 객체에게 초기화에 성공했음을 알림, 앱 객체는 메인 런루프를 시작함.  
}

물론 nib 파일이나 스토리보드를 쓴다면

-(BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions  
{  
    return YES;  
}

로 끝난다.

프로토콜에 의해 앱 델리게이트 객체는 window 속성을 가지게 되고 이것이 아이폰 화면에 표시될 메인 윈도우가 된다. 별도의 nib 파일이 없다면 초기화시점에 window에 루트 뷰 컨트롤러를 인스톨하고 윈도를 화면에 표시하면 된다.

윈도 객체

iOS 앱도 윈도상에서 디스플레이가 표시되며, 윈도는 항상 화면을 가득 채운 상태로 만들어진다. 앱 델리게이트 프로토콜에는 기본적으로 윈도 객체가 프로퍼티로 선언되어 있다. 복수개의 윈도 객체를 쓰는 케이스는 외부 디스플레이에 앱을 표시하는 용도외에는 없다고 봐도 무방하다. 인터페이스 빌더를 사용하면 기본 nib 파일 내에 윈도객체가 정의되며, UIApplicationMain 함수가 실행될 때 자동으로 만들어진다. 그렇지 않다면 위의 예제 코드에서 본 바와 같이 수동으로 만들어서 화면에 노출시켜야 한다.

화면에 가득차도록 윈도 객체를 만들기 위해서는 화면을 나타내는 UIScreen 객체를 사용하며, 주 화면의 크기는 다음과 같이 구한다.

CGRect mainScreenFrame = [[UIScreen mainScreen] bounds];

루트 뷰 컨트롤러

화면에 표시될 최초의 뷰 컨트롤러를 루트 뷰 컨트롤러라고 관습적으로 표현하는데, UIWindow 객체는 rootViewController라는 속성을 가지고 있다. 초기화 시에 앱의 루트 뷰 컨트롤러를 만들어 윈도 객체의 rootViewController로 설정하면 윈도에 루트 뷰 컨트롤러를 인스톨하는 행위가 된다.

루트 뷰 컨트롤러는 UIViewController나 이를 상속받는 클래스의 인스턴스이면 되고, 네비게이션 컨트롤러(UINavigation Controller)는 UIViewController를 상속받는 하위 클래스이므로, 네비게이션 구조를 사용하는 앱이라면 네비게이션 컨트롤러를 사용해도 된다.

즉 네비게이션 구조를 사용하는 앱이라면 루트 뷰 컨트롤러는 네비게이션 컨트롤러의 루트 뷰 컨트롤러가되고, 네비게이션 뷰 컨트롤러 자체는 윈도의 루트 뷰 컨트롤러가 되는 식이다.

RootViewController *rootViewController = [[RootViewController alloc]  
initWithNibName:@"RootViewController" bundle:nil];  
UINavigationController *navControlelr = [[UINavigationController alloc]  
initWithRootViewController:rootViewController];  
[self.window setRootViewController:navContrller];  
[self.window makeKeyAndVisible];

인터페이스 빌더에서 네비게이션 뷰 컨트롤러를 사용하려면 먼저 뷰 컨트롤러를 만든 다음, 해당 뷰 컨트롤러를 선택한 상태에서 “Editor > Embed > Navigation Controller”를 선택하면 해당 뷰 컨트롤러를 감싼 네비게이션 뷰 컨트롤러를 쉽게 만들 수 있다.

앱의 시작점

이미 알고있겠지만, iOS앱은 Objective-C로 만들어지는데, 이 objective-C는 C위에 객체 사이에 메시징을 가능하게 만드는 기능을 얹어놓은 수퍼셋이다. 따라서 Objective-C도 C처럼 main 함수에서부터 출발한다. Xcode 내에서도 main.m 파일은 항상 존재하는데, 여기에 메인 함수가 정의되어 있으며 보통은 다음과 같이 쓰여진다.

# import <UIKit/UIKit.h>

# import "MYAppDelegate.h"

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

메인 함수는 UIApplicationMain 함수를 호출해주고, 그 결과를 리턴한다. 리턴이 일어나는 시점은 UIApplicationMain 함수가 리턴값을 돌려주는 시점이 될 것이고, 이는 앱이 (정상 혹은 비정상적으로) 종료되는 시점이 될 것이다.

UIApplicationMain

UIApplicationMain 함수는 앱의 가장 베이스가 되는 앱 객체를 인스턴스화하고, 앱 번들 내의 Info.plist 파일을 읽어들여 앱의 구성요소들을 만들어낸다. 그리고 스토리보드나 nib 파일을 사용해서 화면을 구성했다면, 메인 인터페이스에 대한 정보는 이 Info.plist파일 내에 기록되어 있으므로, 해당 nib 파일이나 스토리보드 파일을 읽어들여서 구동에 필요한 객체들을 모두 초기화한다. (이 내용이 없다면 앱 델리게이트가 window 객체부터 루트 뷰 컨트롤러등을 만들어서 인스톨하게 된다.)

이 함수의 원형은 다음과 같다.

int UIApplicationMain(int argc, char *argc[], NSString *principalClassName, NSString *delegateClassName);

즉 main 함수의 기본 파라메터들을 모두 받고, 추가적으로 앱 객체의 클래스이름과 앱 델리게이트 객체의 클래스 이름을 받는다. 여기서 principalClassName은 앱 객체의 클래스이름인데, nil을 넘겨주면 UIApplication 클래스를 사용해서 앱 객체를 만든다. 델리게이트 클래스 이름을 넘겨주어야 하는데, 만약 nib 파일 안에 앱 델리게이트 객체가 정의되어 있다면 역시 nil을 넘겨주어도 상관없다.

결론

UIApplicationMain 함수가 앱 객체와 앱 델리게이트의 인스턴스를 자동으로 생성하고 또 Info.plist 파일에 메인 nib 파일이 정의되어 있다면 별도의 코드 없이 화면을 생성할 수 있다. 메인 인터페이스 파일을 지정하지 않았다면 앱 델리게이트에서 UIWindow 및 루트 뷰 컨트롤러를 생성하는 코드를 작성하여 화면을 수동으로 표시해야 한다.