2010년 2월 10일 수요일

iphone programming : Controller 작성

2.8. Controller 작성
앞에서 우리는 Interface Builder 를 완벽히 사용해서 화면 구성을 모두 끝냈고, Outlet 과 Action 이 모두 준비된 Controller 클래스도 생성했다.

프로그램을 완성하기 위해 남은 일은 단 하나, 바로 코드를 작성하는 일이다. 다시 말해서, 이제 정말로 Objective-C 프로그래밍 만이 남은 것이다.
Objective-C 언어에 대해서는 The Objective-C 2.0 Programming Language 와 같은 문서를 먼저 보는 것이 좋다. 이 문서는 그리 길지 않게 주요 사항들이 잘 요약되어 있다. 이 글이 작성되는 시점에서 2008 년 6월 9일 문서가 나와있다.
객 체 지향 개념에 대해서 보다 더 도움이 필요하거나, Objective-C 에서 어떻게 OOP 프로그래밍 개념을 구현하고 있는지에 대해서 정리하고 싶다면 Object-Oriented Programming with Objective-C 문서를 참조하면 큰 도움이 된다. A4 용지 분량으로 40페이지 문서지만, 개념적인 내용으로 이루어져 있고 당연히 영문 문서이기 때문에 OOP 경험이 없는 사람이라면 쉽게 읽혀지지는 않을 것이다.

다 시 언급하지만, Xcode 에서 제공하는 문서 보기 프로그램을 이용해서 여러가지 문서들을 쉽게 찾을 수 있으므로, 항상 애용하도록 하자. 문서 보기 프로그램의 개념은 마치 iTunes 에서 Pod Cast 를 이용하는 것과 비슷하며, 웹 문서로의 링크로 존재하는 부분들도 많이 있다.

이 문서에서는 상세하게 Objective-C 언어에 대해서 살펴보지는 않는다. 단지 간단한 프로그램을 작성하는데에 필요한 부분들만 빠르게 살펴보고, 직접 코드 작성에 이용해 보도록 하겠다. 이렇게 단순하게 몇가지를 살펴보면서 프로그램을 완성하면 전체적인 윤곽을 잡는데에 도움이 될 것이다. 또한, 반드시 각자 Objective-C 언어에 대해서 별도의 문서를 살펴보기 바란다. 다행히, Objective-C 언어는 그렇게 복잡한 문법을 가진 언어가 아니다.

이제 Xcode 의 Workspace 에서 MyHelloViewController.m 파일을 에디터 창에서 열고 살펴보자.
아마도 다음과 같은 형태로 코드가 작성되어 있을 것이다.


#import "MyHelloViewController.h"

@implementation MyHelloViewController
- (IBAction)changeGreeting:(id)sender {
    
}
@end


C언어 비슷하지만 상당히 낮선 형태의 코드가 보인다. #import 구문은 눈치를 보아하니 #include 와 같은 것으로 보이고, 실제로 그렇다.
다 시 한번 말하는데, Objective-C 는 C 언어의 모든 구문을 사용할 수 있다. C 언어 위에 객체지향 관련 사항만 추가되어 확장된 형태다. #include 역시 사용 가능하지만, Objective-C 에서는 #import 를 주로 사용한다. 그 이유는, 중복되는 include 를 컴파일러가 알아서 막아주기 때문이다. 따라서 헤더 파일에 쓸데 없는 중복 회피용 전처리기 구문을 넣지 않아도 된다.

또 한가지 요상한 것이 보이는데, 이른바 골뱅이 문자(@)의 사용이다. @ 문자는 Objective-C 에서만 사용되는 지시문을 사용할 때 함께 사용되는데, 위에서는 @implementation 과 @end 가 보인다.

제일 헷갈리는 것이 
changeGreeting: 이 선언되어 있는 모습이다. 여기서 이것을 자세하게 말하지는 않겠지만, C 언어의 함수 정의로 해석해 보자면
  IBAction changeGreeting( id sender ) { ... }

와 같다고 보면 된다. 왜 이렇게 이상한 형태로 되어있는지, Objective-C 언어 문법을 공부하다 보면 자연스럽게 알게 될 것이다. 우리는 일단 넘어가도록 하자.

C 언어 식으로 말하자면, 버튼을 클릭하면 changeGreeting: 함수가 호출되도록 되어 있다. 그러면, 일단 간단하게 버튼을 클릭하면 화면에 어떤 변화가 일어나도록 코드를 작성해 보자.

우리가 처음 하려던 것은, 버튼을 클릭하면 화면 중간에 있는 라벨의 문구가 변경되도록 만드는 것이었다. 일단 시험을 해보기 위해, 버튼 클릭시 라벨의 문구가 영구적으로 변경되도록 다음과 같이 작성해보자.

- (IBAction)changeGreeting:(id)sender {
    label.text = @"잘가요!";
}

label 의 text 속성 값을 문자열 대입에 의해 변경한다는 것으로 이해한다면 쉽게 읽을 수 있을 것이다. 사실 이것은 Objective-C 방식의 해석으로는 틀린 것이지만, C 언어 프로그래머가 보기에는 마찬가지다.
한 가지 이상한 것은 문자열 따옴표 앞에 @ 문자가 또 다시 나타난다는 점이다. @ 문자가 앞에 있는 문자열은 C 언어의 문자열과는 다르다. 이것은 Objectiv-C 언어에서 사용하는, 문자열 객체라는것 정도로만 이해하고 넘어가도록 하자.

그리고, 다음과 같이 새로운 메소드를 하나 추가하자. 이것은 프로그램이 종료될 때 메모리를 정리하기 위한 구문이다. release 의 자세한 의미는 나중에 살펴보기로 하자.

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

현재까지 작성된 MyHelloViewController.m 소스의 전체 코드는 다음과 같다.

#import "MyHelloViewController.h"


@implementation MyHelloViewController

- (IBAction)changeGreeting:(id)sender {
    label.text = @"잘가요!";
}

- (void)dealloc {
    [textField release];
    [label release];
    [super dealloc];
}
@end

소스 코드를 저장하고서 Build & Run 으로 실행해 보자. 만일 소스코드에 오류가 있다면 오류가 있는 위치에 에러 표시가 나타나서 쉽게 수정할 수 있다. 의도적으로 소스 코드에 에러를 만들어서 Workspace 가 어떤 식으로 에러 표시를 하는지 보는것도 좋다.


프로그램을 실행한 후, 버튼을 클릭하면 라벨의 문구가 소스 코드에서 지정한 대로 변경된다. 물론, 원래 상태로 돌아오도록 하는 기능은 없기 때문에, 다시 초기상태로 돌아가는 방법은 현재로서는 프로그램을 종료하고 다시 실행하는 것 뿐이다. 하지만, 약간만 더 작성하면 우리의 MyHello 프로그램은 원하는 모습대로 동작하는 프로그램으로 완성될 것이다.


현재 버튼을 클릭하면(다르게 말하면 '터치'하면) 라벨의 내용이 변경되도록 코드를 작성해 보았다. 이제는 Text Field 에 입력한 내용으로 라벨의 내용을 변경하도록 작성해 보자. 기본 기능에 의해 텍스트 필드에 문자열을 입력하는 것 까지는 가능하지만, 현재 문자열 입력을 종료하고 키보드를 다시 닫는 동작이 이루어지지 않는 상태다. 먼저 키보드의 Enter 키 처리를 추가하도록 하자.

이것은 조금 성가신 작업이 필요한데, 다시 MyHelloViewcontroller.xib 를 더블 클릭하여 Interface Builder 를 실행해야 한다. 왜냐하면, 앞서 Interface Builder 에서 딱 하나 해주지 않고 미뤄둔 작업이 있기 때문이다.

Interface Builder 에서, View 화면에 있는 텍스트 필드를 마우스 우측 클릭으로 connection 창을 열고서 delegate 연결을 File's Owner 와 연결해준다. 이제는 아래 그림을 보면 다들 쉽게 이해할 수 있을 것이다.

이제 대충 짐작이 가지 않는가? textField 의 delegate 메시지를 우리가 작성중인 MyHelloViewController 에서 받아서 처리하겠다는 것을 명시하는 것이다. 이제 textField 에서 발생하는 어떤 종류의 사건들은 위임 메시지가 되어 우리가 작성하는 컨트롤러 클래스로 전달되게 된다. 그 메시지들 중 처리할 수 있는 것들만 메소드를 만들어서 처리하면 된다.

XIB 파일이 수정되었으면 어떻게 해야 하는지 기억나는가? 딱 한번 해보았기 때문에 많은 사람들이 기억하지 못할 지도 모르겠다. Interface Builder 에서 내용 수정이 일어났기 때문에, File's Owner 아이콘이 선택된 상태에서 메뉴의 'Write Class Files...' 을 선택하여 다시 MyHelloViewController 헤더 파일과 소스 파일을 저장해주어야 한다. 그리고 필히, 저장 확인시 Merge 를 선택해서 지금 작업중인 내용들을 보존해 주기 바란다.(Merge 수행 시 실행되는 소스 Merge 프로그램이 좀 원시적인 것은 아쉽다)

저장한 소스를 확인해 보면 MyHelloViewController.h 헤더 파일에 한가지 내용이 추가된것을 볼 수 있다.

@interface MyViewController : UIViewController 
<UITextFieldDelegate> { ... }

이제는 키보드에 Enter 키 입력이 발생하면 텍스트 필드의 해당 동작에 대한 위임(delegate) 메시지가 발생해서 컨트롤러로 전달되게 되었다. 현재는 이 위임 메시지를 처리하는 곳이 없다. 이것을 처리하려면 다음과 같이 
textFieldShouldReturn: 메소드를 만들어주면 된다. 이 메소드 역시 MyHelloViewController.m 코드에 추가하면 된다.
- (BOOL)textFieldShouldReturn:(UITextField *)theTextField {
    if( theTextField == textField ){
        [textField resignFirstResponder];
    }
    return YES;
}

메소드를 선언하는 형태가 다소 복잡해 보이지만, 지금은 theTextFieldShouldReturn: 이름만 기억하고 넘어가도록 하자.
메소드 안에 있는 if() 문은 지금 이 프로그램에서는 필요 없다. 왜냐하면 if() 문의 조건문을 보면 알겠지만, Enter 키가 입력된 텍스트 필드가 구체적으로 어떤 것인지를 확인해서 
resignFirstResponder: 메 소드를 호출해주고 있기 때문이다. 현재 사용된 텍스트 필드 아이템 객체는 단 하나뿐이므로, 지금 구분하는 것은 사실상 의미가 없다. 하지만 두개 이상의 텍스트 필드가 사용되는 프로그램을 작성한다면, 현재 보여주고 있는 if 문과 같은 처리가 꼭 필요할 것이므로, 기억해 두는 것이 좋다.
resignFirstResponder: 라는 것의 의미는, 현재 텍스트 필드가 키보드를 보여주면서 모든 입력을 우선적으로 받아서 처리하는 First Responder 상태가 되어 있지만, 이제 그것을 포기하고 넘겨주겠다는 것을 의미한다. 따라서 textField 안에 있는 resignFirstResponder: 메소드 안에는 그에 해당하는 처리를 하는 코드가 있을 것이다. 그리고 우리는 YES 를 리턴한다.

이것으로 텍스트 필드에 열려있던 키보드는 다시 닫혀지게 된다. 여기까지 작성한 후, 다시 프로그램을 build 하고 실행해서 시뮬레이터를 실행해 보도록 하자. 이번에는 텍스트 필드에 문자를 입력한 후 엔터키를 눌러서 입력을 종료할 수 있게 되었다.

자, 이제 텍스트 입력이 자유롭게 되었으므로, 버튼의 동작을 원래 목적했던 것으로 완성하자.
Workspace 에서 에디터를 열고 앞서 작성했던 
changeGreeting: 메소드의 내용을 다음과 같이 수정해 보도록 한다.
- (IBAction)changeGreeting:(id)sender {
    NSString *nameString = textField.text;

    NSString *greeting = [[NSString alloc] initWithFormat:@"안녕하세요 %@!", nameString];

    label.text = greeting;
    [greeting release];
}

NSString 은 Cocoa Foundation 에서 정의된 문자열 객체로서, Mac 프로그래밍에서 모든 문자열은 이것으로 사용한다고 말해도 틀리지 않다. 메소드 첫 줄에서 textField 의 문자열을 nameString 으로 가져오고 있다. '가져온다'는 표현은 상당히 애매한 표현이다. 나중에 NSString 에 대한 각종 정보를 The Objective-C 2.0 Programming Language 문서나 String Programming Guide for Cocoa 문서 등을 참조하여 확인해 보기를 권한다.
Cocoa Foundation Class 들의 이름은 전부 대문자 NS 로 시작한다. 당연히, 이것은 NeXT Step 의 머릿글자다. 새로 만들어진 iPhone 용 Foundation 들의 이름은 더 이상 NS 로 시작하지 않지만, 기본적인 Cocoa Foundation 들의 모든 Class 는 이 규칙을 지킨 채 남아있다.

initWithFormat: 메소드는 마치 printf 처럼 문자열에 의해 지정된 형식으로 새로운 문자열을 만들기 위해 사용된다. 이렇게 해서 greeting 이라는 이름의 새로운 문자열 객체를 alloc 으로 만들고 있다. 새로 만들어진 문자열에 의해 Label 의 내용을 수정한 다음, greeting 은 release 를 호출해 주어야 한다.

이제 프로그램을 Build 해서 시뮬레이터로 직접 테스트 해 보자. 임의의 문자열을 입력한 후 버튼을 클릭하면 라벨의 문구 내용이 변경된다. 우리의 첫 번째 iPhone Application 이 완성된 듯 하다.


여기에 약간의 코드를 더 추가해서 프로그램을 완결짓도록 하자.
다음은 일부 코드가 추가된 MyHelloViewcontroller.m/h 소스 코드의 전체 내용이다.
#import <UIKit/UIKit.h>

@interface MyViewController : UIViewController <UITextFieldDelegate> {
    IBOutlet UITextField *textField;
    IBOutlet UILabel *label;
    
NSString *string;
}

- (IBAction)changeGreeting:(id)sender;

@end

#import "MyViewController.h"

@implementation MyViewController

- (IBAction)changeGreeting:(id)sender {
    
string = textField.text;

    NSString *nameString = string;
    
if ([nameString length] == 0)  nameString = @"이름은?";

    NSString *greeting = [[NSString alloc] initWithFormat:@"안녕하세요 %@!", nameString];

    label.text = greeting;
    [greeting release];
}

- (BOOL)textFieldShouldReturn:(UITextField *)theTextField {
    if (theTextField == textField) {
        [textField resignFirstResponder];
    }
    return YES;
}

- (void)dealloc {
    [textField release];
    [label release];
    
[string release];
    [super dealloc];
}
@end

그냥 사용해도 되는 textField.text 의 내용을 왜 굳이 string 이라는 이름의 인스턴스 변수를 만들어서 한번 더 거치는지, 그냥 코드를 보면 잘 이해가 되지는 않는다. 이것은 Apple 의 Tutorial 에서 하나의 예로서 보여주고 있는 방법으로서, 가장 단순한 방식으로 MVC 패턴의 Model 을 구현하여 다루고 있는 방법이다. 일반적으로 컨트롤러는 응용 프로그램의 정보를 자신의 모델 객체에서 관리하며, UI 아이템에 데이터를 저장해서 사용하지 않아야 한다. 일단 지금은 이정도로만 넘어가도록 하자. 이렇게 새로 추가된 string 객체 때문에 dealloc 메소드 안에서 release 처리가 추가되었다.

참고로 덧붙여 말하자면, 본 프로그램은 Tutorial 문서에 기반하고 있지만 
@property, @synthesize 구문은 사용하지 않고 작성되었다. 궁금하다면 Tutorial 문서에 있는 원본 소스코드를 참조하여 비교해 보기 바란다. 이 두 가지 구문에 대한 것은 조금 뒤에서 Objective-C 언어 문법을 정리하면서 다루도록 하겠다.


자, 이렇게 iPhone/iPod Touch 용 Application 을 완성했다.
하지만, 한가지 좀 더 재미있는 것을 추가해 보자. (완성이라고 말하고서는 자꾸 추가해서 미안하다. 하하핫.) 이것은 Tutorial 문서에서는 다루지 않는 것이지만, 간단한 작업이고 결과는 효과적이다.

시뮬레이터를 좌/우로 90도 돌려보자. 방향을 바꾸더라고 아무런 일도 일어나지 않는다. 당연히, 방향 전환에 대해서 우리의 프로그램이 처리하지 않고 있기 때문이다.

이제 다음과 같은 메소드를 컨트롤러에 추가하라.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations.
    return YES;
}

shouldAutorotateToInterfaceOrientation: 메시지는 iPhone 의 자세가 90도 단위로 변경되면 View 의 컨트롤러에게 전달된다. 컨트롤러에서 이 메시지를 받아서 YES 를 return 하면, View 는 자신의 형태를 현재 위치에 맞게 '변신' 한다. 당연히, View 위에 뿌리를 내리고 살림을 차리고 있던 모든 녀석들도 덩달아 돌아가거나 크기를 변경하게 된다.

아래 그림처럼 시뮬레이터를 90도 기울여보자.

이 그림을 보면, 가장 위에 있는 textField 는 변경된 가로 길이에 맞게 자신의 길이를 조절했다. 그러나, 아마 여러분의 프로그램은 그렇지 못할 것이다. 대부분 가로 길이가 고정된 채로 남아있고, 오른쪽에 빈 공간이 생겼으리라 예상된다.
왜 그럴까? 그것은 필자가 Interface Builder 에서 textField 를 화면에 배치할 때 뭔가 해주었기 때문이다.

위에 그림에서 텍스트 필드는 크기를 화면에 맞게 조정했지만, 라벨과 버튼은 고정된 자리를 지키고 있기 때문에 중앙에서 벗어난 자리에 있게 된다. 별로 보기 좋지 않으므로 이것도 항상 가운데 정렬을 유지하도록 수정하자. 수정은 당연히 Interface Builder 에서 해주면 된다.

아주 간단하다. 배치되어 있는 버튼을 선택하고 아래 그림처럼 Size 속성 탭을 보자. 이 그림에서 주목할 것은 가운데 영역의 
Autosizing 부분이다. 현재 버튼의 자동 위치,크기 설정을 붉은색 선을 클릭해서 켜고 끄는 것으로 모두 지정할 수 있다. 즉, 아래 그림처럼 설정하면 상단으로부터의 거리만 고정된 채, 화면 좌/우측과의 거리는 자동으로 같은 비율을 유지하게 된다. 같은 방법으로 Label 아이템의 Autosizing 설정도 변경해 보도록 하자.

이렇게 아이템의 설정을 변경하고, XIB 파일을 저장한다. 단, 소스 코드에 영향을 미치는 수정 사항이 아니므로, Write Class Files... 는 해줄 필요 없다.

이제 Xcode 에서 프로젝트를 다시 Build 한 후에 실행해서 그 결과를 확인해 보자. 어떤가? 복잡한 화면 처리도 View 에서 알아서 처리해 주기 때문에, Application 프로그래머는 불필요한 시간 낭비를 하지 않아도 된다. 이제 우리의 프로그램은 화면을 옆으로 돌려도 보기 좋은 레이아웃을 유지한다.

당연히, 화면이 옆으로 변경되었어도 키보드 입력 등 모든 동작은 정상적으로 이루어진다.

이로서 (이번엔 진짜로) 우리의 첫 번째 iPhone Application 제작을 끝마칠 때가 왔다. 모두 iPhone 프로그래밍의 매력을 충분히 느낄 수 있었으리라 생각한다. 이 프로그램을 각자 살펴보고 원하는대로 수정하면서 Xcode 와 Interface Builder 사용에 익숙해지기 바란다. 그리고 궁금한 점은 Apple 에서 제공하는 각종 문서를 검색하면 빠르게 적응할 수 있을 것이다.

특히, Objective-C 언어에 대한 사항은 위에서 계속 언급한 별도의 문서들을 따로 참조하는 것이 좋다. 지금까지 우리는 Objective-C 문법에 대한 사항은 자세히 살펴보지 않았는데, 이제 언어 문법과 개념적인 내용에 대해 다음에 간단하게 정리하고, 다음 프로젝트로 진행하는 것이 좋으리라 생각된다.

내 생각이지만, C++ 언어보다 훨씬 쉽다.

댓글 없음:

댓글 쓰기