본문 바로가기

Software/iOS & Objective-C

iPhone Application Life cylce

[본글은 http://blog.naver.com/PostList.nhn?blogId=gboarder 여기서 포스팅 했습니다]

아이폰 관련 어플 개발을 하다보면, 이벤트 루프에 대해서 고민을 하게 되는데, 이벤트 루프에 대해서 설명하지 않으면 어렵다. 그래서 이참이 이에 대해서 나름대로 정리해본 내용을 공개한다.


이 내용은 iOS Application Programming Guide의 내용을 이해하고 있다면 불필요한 내용이지만, 대부분 개발자를 만나보면, 제대로 이해하고 있는 분들이 극히 드믈다. 본인도 이 부분때문에 다시 공부해서 이렇게 정리해본 것이다.

어플리케이션 라이프사이클은 시작부터 종료까지의 일련 흐름으로서 iOS Application Programming Guide에 나온 내용이기때문이지 대부분 서적에는 빠져 있다.

원초적인 내용이라서 어플리케이션을 만들기 위한 초기 설명이지만, 이를 설명하기 위해서는 그 만큼 다양한 지식이 필요할지도 모르겠지만, 실제로 iOS Application Programming Guide에서도 'iOS에서는 UIKit Framework를 사용하여 네이트브 어플리케이션을 개발합니다'라고 말하는 부분에서 개발 초보자들에게 이야기 한다면 당연히 머리속은 ???만 가득찹니다.

하여튼 Delegate나 property같은 내용이 나오는데 자세히 설명하지 못하더라도 각자 찾아서 공부하고 이해하기 바란다.

어플리케이션 라이프사이클

어플리케이션 라이프사이클은 다음과 같은 순서로 진행된다.

1) 어플리케이션 시작
2) main()함수 호출(UIApplicationMain() 함수 호출 포함)
3) 이벤트 루프  (이벤트 처리 사이클)
4) 어플리케이션 종료 체크
5) 어플리케이션 종료


1) 어플리케이션 시작

-  홈화면에서 어플리케이션 아이콘 탭
iPhone 홈화면에서 어플리케이션 아이콘을 탭하는 것에 의해 어플리케이션이 시작된다.

-트래지션그래픽스 표시
어플리케이션의 실제 실행화면을 표시하는 동안, 화면 가운데부터 어플리케이션 화면이 확대되는 애니메이션이 표시된다. 트랜지션그래픽스 이미지는 Resources 폴더의 Default.png가 사용된다. Default.png가 없는 경우, 검정 화면으로 표시된다. (상태바가 있는 경우, 애니메이션을 하고 있음)

Default.png는 Resources폴더에 넣기만 해도 적용된다. 본인이 iPhone Simulator로 변경하거나 삭제할 경우, 지운 이미지가 나오는경, Build메뉴에서 [Clean]을 선택하여 지운 후 사용하면 적용된다. Default.png는 어디까지나 트렌지션그래픽까지의 이미지로 애니메이션이 끝나면, nib파일에 설정한 배경이미지(Background.png)가 사용된다. 배경 이미지가 설정되어 있지 않다면, Windows-based인 경우 흰색, View-Based인 경우, 그레이 화면이 나온다.

트랜지션그래픽스와 다르게 Resources 폴더에 Backgroudn.png를 넣는것만으로는 반영되지 않고 nib파일로 설정할 필요가 있다.

2) main()함수 호출

main()함수는 어플리케이션 시작시, 최초로 읽히는 Other Sources폴더내 main.m 파일에 대해서 말하는 것으로 어플리케이션 초기화를 처리한다.

2-1) UIKit Framework 임포트
2-2) main()함수 인수
2-3) Autorelease pool생성과 해지
2-4) UIApplicationMain()함수 호출
2-5) 어플리케이션 Delegate 생성
2-6) UIApplicationDelegate 프로토콜
2-7) AppDelegate선언과 실행

#import <UIKit/UIKit.h>

int main(int argc, char *argv[]){
  NSAutoreleasePool *pool = [[NSAutoreleaasePool alloc] init];
  int retVal = UIApplicationMain(argc, argv, nil, nil);
  [pool release];
  release retVal;

2-1) UIKit Framework 임포트
모든 아이폰 어플리케이션은 UIKit 프레임워크를 사요해서 만든다라는 말 그대로 UIKit.h가 임포트 되어 있다.

2-2) main()함수 인수
main()함수의 인수 argc, argv는 커멘드라인 인수로 불리는 것으로 커멘드라인에서 main()함수를 호출할 때 인수를 넘길 수 있는 C언어 사양이다. 사실 UIApplicationMain에 넘겨주는 것 때문에 무엇때문에 이렇게 처리하는지 고민을 상당히 해보았지만, 아무런 성과는 없었다.

실제로는 아이폰 어플리케이션은 커멘드라인으로 부터 호출되는 것이 없기 때문에 무시해도 상관은 없을듯 보여진다. 추측하건데 일부러 UIApplicationMain에 넘겨주는 것은 어떤 용도가 있다고 생각되지만, 알 수 없었다.

2-3) AutoreleasePool 생성과 해지
NSAutoreleasePool은 자동 릴리즈 풀을 만드는 메소드로  자동 릴리즈는 프로그램 내부적으로 일시적으로 사용하고 나서 불필요하게된 인스턴스가 자주 발생하는 데 이런 것들을 잊지 않고 릴리즈 하지 않게 되면 메모리를 계속 사용하게 되어 , 메모리릭(memory leak) 상태가 된다.

사실 코딩을 하다보면, 릴리즈하긴 하는데 이를 까먹고 못하는 경우가 종종 있다. 이런 번거로움을 해결하기 위한 것이 바로 Auto Release로 이에 등록된 인스턴스를 풀에 모아두고 한번에 정리하여 릴리즈 하는 것이다.
인스턴스 생성이나 초기화시, autorelease라는 플래그를 선언하면 등록되므로 일시적으로 사용하는 인스턴스가 많다면 일일이 릴리즈하는 번거로움을 막을 수 있다.

단, retain으로 레퍼런스 카운트를 늘린 경우에는 당연히 릴리즈 횟수를 맞추지 않으면 안된다. 또한 템플릿으로 만든 프로젝트에서는 main()함수에서 Autorel;easePool이 설정되지만, 이 기본 상태에서는 어플리케이션 종료시에 정리하므로 대량으로 등록하는 경우, 메모리가 부족하게 되므로 주의해야 한다. 대량으로 사용되는 곳에서는 별로 AutoreleasePool을 설정하는 것이 좋다고 본다.

NSAutoreleasePool은 이벤트루프가 한번 돌때마다 생성과 해지를 반복한다. 그렇기 때문에 어플리케이션 종료까지 객체가 모이지는 않고 대부분 Autorelease가 끝난 객체는 메소드가 종료된 후 해지된다.

실제 main()함수에서는

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

NSAutoreleasePool 인스턴스 pool을 생성 및 초기화 하고 발로 풀을 만들고

[pool relesse];를 선언해여 해지한다. (왜 바로 릴리즈를 할까?)

2-4) UIApplicationMain() 함수 호출
실제 어플리케이션 초기화를 실시하는 것이 UIApplicationMain()함수로 UIApplication 인스턴스를 생성한다. 

int UIApplicationMain {
  int argc;
  char *argv[];
  NSString *principalClassName;
  NSString *delegateClassName;
};

main()함수가 호출하는 어플리케이션 객체와 어플리케이션 delegate의 생성, 어플리케이션 실행루프를 포함한 이벤트루프의 설정을 처리하는 함수이다. 리턴값은 int형을 지정해 있지만, 이 함수는 리턴값을 돌려주지 않는다.

사용자가 홈버튼을 눌러 아이폰 어플리케이션을 종료시킬 때, 0인수와 종료 시스템함수를 불러 어플리케이션을 바로 종료한다. 어플리케이션 Info.plist 파일로, NSMainNibFile키에 대응하는 nib파일이 설정되어 있다면 이 nib파일을 읽어들인다.

argc - argv인수의 항목수를 지정, main()함수로부터 상속 받음
argv - 인수 배열을 지정, main()함수로부터 상속받음
principalClassName - UIApplication클래스 또는 하위클래스 이름 지정하며, nil인 경우 UIApplication이 된다.
delegateClassName - 어플리케이션 delegate를 인스턴스화하는 클래스명 지정하며, principalClass로 UIApplication를 하위클래스로 지정 했을 때, 하위클래스 Delegate를 지정할 필요가 있다. nil인 경우, 어플리케이션 메인 nib파일로부터 Delegate객체를 읽어들인다.

기본적으로 3,4 인수 모두 nil이 지정되어 있기 때문에 UIApplication클래스에서 어플레케이션 객체가 4인수로 Info.plist의 Main nib file base name 으로 설정되어 있는 nib파일이다.
MainWindows.xib파일로 설정되어 있다면 이를 열어보자. Untitled App Delegate라는 delegate가 만들어져 있게 된다.

2-5) 어플리케이션 Delegate 생성
솔직히 이해가 되지 않는 Delegate이지만, 기억은 해두어야할 것이라서 소개한다.
Delegate는 대리자, 위탁이라고 번역할 수 있는 것으로 메시지를 받은 객체가 스스로 처리할 수 없는 좀더 가공하고 싶은 경우에 Delegate객체에 처리를 요청한다라는 구조이다.

스스로 처리할 수 없는 메시지를 처리한다는 것은 버튼의 On/OFF를 표시하는 객체가 조작여부를 판단하는 경우, 원래 동작이외는 Delegate에 맡겨서 독립성을 유지한다라는 의미를 가지고 있다.

좀더 가공하여 처리하는 것도 자신이 가지고 있지 않은 기능을 추가하여 처리하는 것으로 복잡한 객체의 하위 클래스화를 피하는 방법이기도 하다.

(여기서 설명된 Delegate의 개념이 모호함)
처리할 수 없는 좀더 가공이라는 의미가 아니라, 너무 많은 처리(즉 Method) 한 클래스내에서  처리하게 되면 클래스로서의 독립성을 떨어트릴 뿐더러, 객체와 객체간 통신시 좀더 유기적인 프로그램의 Cycle을 구현하기 위한 하나의 방식이라고 생각한다.

구지 좀더 쉽게 표현하자면 내가 어떠한 Method를 통해 어떠한 일을 처리한다고 가정했을 경우, 그 Mothod가 포함된 클래스를 상속받아 불필요한 데이터까지 포함시키는 것은 무식한 짓이다. 따라서 Delegate를 통해 위임하여 특정 롤에 대한 업무는 니가 처리해! 라고 생각하면 될듯 싶다.

Delegate는 개발자가 임의적으로 배치할 수 있지만, 템플릿으로 프로젝트를 만들면 자동적으로 ~AppDelegate.h와 ~AppDelegate.m이 만들어진다. 이 안에 어플리케이션 초기실행 상태로 준비와 종료시 보존되지 못한 데이터나 어플리케이션 상태를 보존하는 처리를 작성한다.]

어플리케이션 Delegate객체는 UIApplicationDelegate프로토콜을 채용해야 한다.

2-6) UIApplicationDelegate 프로토콜
UIApplicationDelegate 프로토콜은 싱글톤의 UIApplication 객체의 delegate로 선언하는 메소드 이다. 싱글톤은 인스턴스가 한개만 만들어지는 클래스로 어디에서 호출해도 같은 인스턴스를 리턴한다.

이 메소드를 실행하면, 어플리케이션 시작과 종료, 메모리 부족 경고, 상태방향변경등의 시스템 이벤트를 delegate로 응답 할 수 있다. iPhone OS 3.0이상에서는 리모트 통지 관련 메소드나 application:didFinishLaunchingWithOption: 가 추가되어 이 메소드로는 application:didFinishLaunching 대신하여 호출할 수 있고  이 메소드는 다음 2가지 상황을 설정 한다.

* iOS가 어플리케이션에 리모트 Notification을 전달하여 사용자가 해당 Notification에 응답하여 실행한다.

* UIApplication의 openURL:메소드로 URL 스킴으로부터 resource tpye을 구분하여 다른 어플리케이션으로 실행한다.

일반적인 경우, 실행하는 이유가 다르므로 다른 사용자 인터페이스로 어플리케이션을 선택한다.

UIApplication은 application:didFinishLaunchingWithOptions:의 옵션으로 지정되어 있는 사전(NSDictionary객체)를 리턴한다. 어플리케이션을 실행하는 요인이되는 사전에는 2가지가 있는데 하나는 openURL메소드를 호출하는 어플리케이션 번들ID나 URL객체를 포함한 Dictionary, 하나는 리모트 Notification에 포함된 Dictionary 이다.

이 메소드가 delegate를 만드는 경우, 시작할 때 아래순서로 실행된다.

application:didFinishLaunchingWithOptions:
applicationDidBecomeActive:

대체로 

applicationDidFinishLaunching:
applicationDidBecomeActive:
application:handleOpenURL:

이처럼 푸시 Notification을 받아 어플리케이션을 실행 할 때, application:didFinishLaunchingWithOption 메소드와 
application:didReceiveRemoteNotification: 메소드 양쪽 모두를 실행하면, 마지막 메소드는 실행되지 않는다.

양쪽 위치에서 리모트 Notification 을 처리할 필요는 있다.

2-7) AppDelegate선언과 실행 
Window-based Application의 Untitled라는 프로젝트를 예로 보면 어플리케이션 Delegate 내용을 살펴보자.

UntitledAppDelegate.h

 #import <UIKit/UIKit.h>

@interface UntitledAppDelegate: NSObject <UIApplicationDelegate> {
  UIWindows *window;
}

@property(nonatomic, retain) IBOutlet UIWindow *window;

@end

@interface로 UntitledAppDelegate 클래스가 선언되고 있다. NSObject 클래스로 부터 상속을 받고 있으며, UIApplicationDelegate 프로토콜을 사용한다 명시되어 있다.
프로퍼티 속성은 nonatomic(비동기), retain(카운트 증가-[사용])이고 IBOutlet은 Interface Builder에 연결하기 위한 키워드로 아웃렛으로 취급하는 것을 의미한다.


 UntitledAppDelegate.m

 #import "UntitledAppDelegate.h"

@implementation UntitledAppDelegate

@synthersize window;

-(BOOL)application:(UIApplication *)applicationdidFinishLaunchingWithOptions: (NSDictionary *)launchOption{
  //어플리케이션 실행 후 사용자 설정이 필요한 경우 작성함
  [window makeKeyAndVisible]; // 창 표시
  return YES;
}

-(void)dealloc{
  [window release];
  [super dealloc];
}

applicationdidFinishLaunchingWithOptions
리모트 Notification이나 URL리소스를 여는 것으로 어플리케이션을 실행하는 것을 delegate에 전달한다. 어플리케이션 URL리소스를 처리할 수 없었던 경우 NO, 처리 할 수 있는 경우 YES를 리턴한다. 리모트 Notification 결과에 의해 어플리케이션이 시작했을 경우, 이 리턴값은 무시된다.


applicationdidFinishLaunching대신 아래 메소드를 사용하는 것을 추천한다.

application: Delegate하는 어플리케이션 객체 지정

launchingOption : 다음 2가지 상황에 관련된 정보를 포함한 Dictionary 지정
    - iOS가 어플리케이션에 원격으로 전달하여 경고 메시지 표시를 통해 어플리케이션을 
      시작하는 경우
    - URL리소스를 스킴하여 구분하여 다른 어플리케이션을 여는 경우
전자의 경우 launchOption Dictionary은 Notification에대한 Dictionary를 포함하고
후자의 경우 URL 객체와 열고자 하는 어플리케이션 번들 ID를 표시한다. 이 Dictionary는 사용자가 어플리케이션 아이콘을 탭할 때 nil된다.

-makeKeyAndVisible
-(void)makeKeyAndVisible

키 윈도우를 만들어 렌더링한다. 이는 상당히 편리한 메소드로 리시버의 메인윈도우를 만들어 다른 윈도운보다 먼저 표시한다. UIView 프로퍼티 hidden을 사용하고 창을 숨길 수도 있다.

3) 이벤트 처리 사이클

사실 주제처럼 이벤트 루프에 대해서 설명하고자 이렇게 내용이 길어졌는데 글을 쓰면서 점점 미궁속으로 빠져드는 느낌이 들었다. 그래서 일단 이 글을 쓰는데 참고한 
iOS Application Programming Guide의 이벤트 처리 사이클에 대한 내용을 설명하까 했는데 본인도 이해가 되지 않는 부분이 있어 포기했다. 할 수 없이 일반적인 이벤트 루프 개념만 소개 한다.

- 이벤트 루프 개념
우선 이벤트 구동(이벤트 드리븐) 이라는 것부터 설명하면, 이벤트라는 것은 화면터치, 기기 회전등의 사용자가 하는 일련의 행위나 전화가 온다는 등의 시스템으로 부터의 Notification을 포함한 프로그램에 대한 메시징이다.

이벤트 발생에 의한 메시지를 프로그램이 받아 응답하는 것을 이벤트 구동이라고 말한다. 프로그램은 이벤트가 발생할 때까지 대기상태로 아무것도 하지 않지만, 항상 이벤트를 받을 준비를 하고 있어, 이벤트를 받으면 응답하고 대기하는 것을 반복한다. 이를 이벤트 루프라고 말한다.

여러개의 이벤트가 연속해서 발생할 경우, 어플리케이션에서는 이벤트 큐로 순서대로 이벤트를 꺼내 처리한다. iOS의 경우, 이벤트는 UIApplicationMain 객체 이벤트 루프에게 넘긴다. UIApplicationMain 객체는 Delegate와 연동되어 받은 이벤트를 알맞은 객체로 처리시킨다.

참고로 좀더 쉽게 풀어갈 수 있을것 같아서 찾아본 것이 UIApplication 클래스에 대한 개요를 소개한다. UIApplication 클래스는 iOS로 실행하는 어플리케이션 제어와 조정을 집중 관리하는 것으로 모든 어플리케이션은 반드시 UIApplication의 인스턴스를 가진다. 어플리케이션이 실행하면 UIApplicationMain함수가 불려 싱글톤의 UIApplication객체가 만들어진다. 이후 sharedApplication클래스 메소드로 이객체에 접근할 수 있게 된다.

UIApplication 객체의 주요 역할은 사용자로부터 이벤트를 처음 받는 것이고 컨트롤 객체(UIControl)에 의해 알맞은 지정객체에 액션 메시지를 보낸다. 또한 UIApplication 객체는 어플리케이션이 현재 열려 있는 모든 윈도우(UIWindow객체)의 목록을 보관유지하여 이를 통해 어플리케이션 UIView객체를 얻는다.

어플리케이션 객체는 일반적인 Delegate를 할당하여 어플리케이션에 중요한 런타임 이벤트(메모리 부족, 어플리케이션 실행 및 종료 등)를 알려 응답할 수 있는 기회를 준다. 어플리케이션은 openURL메소드로 보내진 메일이나 이미지 파일등의  리소스를 협조 처리할 수 있다. iOS 3.0에서는 리모트 Notification 등록, un-do-re-do UI 설치된 어플리케이션이 URL을 열지 결정된다 등이 UIApplication 메소드에 추가되고 있다.(뭔소리지.....;;???)

UIApplication은 하나 이상의 메소드를 실행하는 UIApplicationDelegate 프로토콜을 명시한 Delegate를 정의한다. UIApplication과 UIApplicationDelegate의 인터페이스에 의해 디바이스 고유 동작을 관리할 수 있다. 이것에 의해 디바이스 방향에 의해서 인터페이스 방향을 바꾸거나, 사용자 이벤트를 일시적으로 보류하는 것, 근접센서에 의해 화면 On/Off하는 등의 어플리케이션 응답을 제어할 수 있다.


 






반응형