2010년 2월 10일 수요일

iphone Programming : Objective-C

3. Objective-C 쾌속 유람기
이번 장에서는 Xcode 와 Interface Builder 를 잠시 떠나서 Objective-C 프로그래밍 언어에 대해 살펴보도록 하겠다. 아무리 Dummy 를 위한 문서라지만, iPhone Programming 을 하는데 있어서 Objective-C 언어의 기본에 대해 전혀 살펴보지 않는다는 것이 무리가 있어 보인다. 어차피 iPhone 이건 Mac OS X 이건 간에 Apple 의 세상에서 프로그래밍을 하기로 마음먹은 이상, Objective-C 언어와 친해지지 않을 수 없는 운명이라고 보면 된다. Objective-C 가 마음에 들지 않는다면, iPhone SDK 프로그래밍은 포기하고 사파리에서 동작하는 웹 프로그래밍에 집중하는 편이 훨씬 올바른 선택이다.
매킨토시와 iPhone Application 프로그래밍에 흥미가 있거나 꼭 익혀야 겠다고 생각한다면, 다른 생각 하지 말고 Objective-C 와 친해질 궁리를 하자.

3 장에서는 간단한 iPhone SDK 프로그래밍에 필요한 만큼만 Objective-C 언어에 대해 압축해서 훓어보고 지나가도록 하겠다. 따라서 조금이라도 상세한 내용이나 자세한 설명은 생략되어 있다. 하지만 이렇게 요약된 내용이 처음 접하는 사람들에게는 오히려 더 도움이 될 수도 있다. 처음부터 상세한 내용을 파고 들면서 익히기에는 시간도 부족하고, 그렇게 해서는 금방 지치기 때문이다.


3.1. 들어가면서


다음과 같이 Objective-C 언어의 특징을 두서없이 요약해보았다.

C 언어에 OOP 확장 기능을 추가했다.
MVC(Model-View-Controller) 패턴을 주로 사용한다.
메시지 전송(send) / 수신(receive) 개념이 기본이 된다.
상당히 동적인(Dynamic) 언어다.
클래스 디자인에 Get/Put 개념이 많이 사용된다.


이외에도 더 여러가지 특징을 나열할 수 있다. Objective-C 언어는 SmallTalk 계열의 OOP 언어로 분류된다. 거의 모든 기본 개념이 SmallTalk 을 기본으로 C 언어를 확장한 것이다. 또한 현재는 존재하지도 않는 클래스에 대해서도 프로그래밍이 가능하여, 진정한 모듈러 프로그래밍이 가능한 언어다.

Dynamic 한 특성이 많아서 상당히 많은 동작이 Run-time 에 이루어지게 된다. 이것은 과거 CPU 성능이 지금과 같이 않을때 실행 속도면에서 약점으로 작용했다. 그러나 이제 이런것은 고려 대상이 아니다. iPhone 같은 작은 디바이스에서 Mac OS 가 동작하는 세상인 것이다. 앞으로는 Objective-C 와 같이 진정한 OOP 프로그래밍 언어가 환영받는 시대가 오지 않을까 생각해본다.

3.2. Object 와 id

한마디로 말해서 Objective-C 는 이름처럼 객체(Object)로 C 프로그램을 작성하는 언어다. Object 는 무엇인가? 사실 이건 완전히 선문답이다. 객체가 무엇이냐는 질문으로 시작할 수 밖에 없지만, 반대로 객체가 무엇인지 알면 거의 전부 다 아는것과 같다.

따라서 지금은 단순하게 짚고 넘어갈 수 밖에 없다. 객체(Object)는 어떤 자료(data)와 그 자료를 처리하는데에 관련된 특정 동작들이 함께 묶여있는 단위다. 프로그래밍 언어마다 용어는 조금씩 차이가 있는데, 객체의 자료는 보통 인스턴스 변수(
Instance Variable)가 되고, 동작은 메소드(method)가 된다. C++ 혹은 Java 와 같은 프로그래밍 언어가 일반화 되었기 때문에, 많은 프로그래머들에게 이런 개념은 익숙할 것이다.

Objective-C 에서는, 객체를 구분하는데에 사용하는 구분자가 있다. 이것은 별도의 형(type)으로 선언되어 있으며, 이 type의 이름이 
id 이다. 어떤 객체로의 포인터 처럼 사용하면 된다. 실제로는 객체가 가지고 있는 어떤 고유값이 저장된 변수로의 포인터이다.

따라서 id 는 NULL 값을 가질 수 있다. id 가 가지는 NULL 값은 별도로 
nil 이라고 하며, 따로 정의되어 있다. C 언어에서 포인터에 NULL 을 사용하듯이, id 변수에 nil 을 사용하면 된다.

id 만으로는 객체가 어떤 종류의 객체인지 알 수는 없다. id 에 객체의 형태에 대한 어떤 정보도 담겨있지 않으며, 단지 객체라는 것을 알려줄 뿐이다. 이것은 컴파일 시에 객체에 대한 상세한 정보가 아무것도 없다는 뜻이다.
따라서 모든 객체는 실행될 때(Runtime) 자기 자신의 매소드, 인스턴스 변수 등의 정보를 제공할 수 있어야 한다. 이런 특성으로 객체는 동적으로 형변환 된다. (
Dynamic Typing)

3.3. 메시지? Message!

어떤 객체에게 무엇인가 지시하려면, '메시지'를 보내면 된다. '함수 호출' 이라는 것은 완전히 잊어버리고, 지금부터는 모두 메시지 전달로 생각하자. 다음과 같이 대괄호를 사용한 문법을 사용한다.

[ receiver message ]

우리는 앞에서 MyHello 프로그램을 작성하면서, 이렇게 생긴 문법의 소스 코드를 이미 많이 봤다. 다시 간단하게 정리해보자면, myRect 라는 이름의 객체에게 display 라는 메시지를 전달하고자 한다면,

[ myRect display ];

이렇게 된다. 조금 생각해보면, myRect 객체에는 display 라는 이름의 메소드를 가지고 있다. 따라서 이것은 그냥 
"myRect 객체에 있는 display 메소드를 호출한다" 라고 말해도 마찬가지라고 생각된다. 그렇지 않은가?
Objective-C 를 처음 접해보면 그런 생각이 드는것이 당연하다. 하지만 이것은 엄연히 함수 호출과는 다르다. 예를 들어서 myRect 안에 display 라는 이름의 메소드가 없어도 컴파일 시에 에러가 발생하지도 않으며, 실행시에도 즉각적인 오동작을 일으키지 않는다. 단지 myRect 객체는 자기가 받아들이지 못하는 메시지를 무시할 뿐이다.

조금 더 메시지 형식을 살펴보자. 함수 호출과 마찬가지로 메시지 전달시에도 하나 이상의 파라메터를 전달하는 것이 가능하다. 그러나 그 형식은 C 언어에서 함수 호출하는 것과는 좀 다르다. setWidth 라는 메시지로 값을 설정하는 경우를 예로 보자.

[ myRect setWidth:20.0 ];

보는 것 처럼 콜론 문자를 사용하여 넘겨주는 값을 지정한다. 만일 하나 이상의 파라메터를 전달하는 경우는 어떻게 될까? 사각형의 좌표와 크기를 설정하는 메시지를 가정해 보자.

[ myRect setLeftX:10.0 leftY:5.0 width:20.0 height:15.0 ];

네 개의 파라메터를 전달하고 있는데, 각 파라메터는 전부 이름을 명시하고 있다. 이것은 생략할 수 있는 선택사항이 아니다. 이것을 굳이 C 언어 함수 호출 형태로 변경해보자면 다음과 같을지도 모르겠다.

my_rect_set(10.0, 5.0, 20.0, 15.0);

Objective-C 의 메시지 전달 형식이 복잡해 보이지만, 조금만 사용해 보면 C 언어보다 훨씬 실수가 적고 소스 코드의 이해도가 높다는 것을 알게 된다.
당연히, 메시지를 받은 객체는 리턴 값을 반환할 수 있다.

rectColor = [myRect getColor];

메시지에 대해서는 Apple 문서에서 
Dynamic Binding 에 대한 내용을 함께 참고하기 바란다. (Polymophism 에 대한 부분도 참고하라) 그러면 메시지 전달 메카니즘에 의해 어떻게 Objective-C 가 동적인 특징을 가지게 되는지 이해할 수 있다.

3.4. Class


일반적으로 많이 알고 있는 C++ 혹은 Java 처럼, Objective-C 역시 Class 선언을 사용한다. Class 이름은 프로그램 소스 코드에서 type 과 마찬가지로 사용하면 된다. 따라서 다음과 같은 sizeof 연산자 사용도 가능하다.

int i = sizeof(MyNewClass);

또한, 인스턴스 객체로의 포인터를 다음과 같이 정의할 수도 있다.

MyNewClass   *myClass;

앞에서 이미 클래스로의 포인터는 id 라는 형으로 다룬다고 말했었다. 이렇게 특정 클래스 이름으로 포인터를 지정하는 것을 Static Typing 이라고 하는데, 사실 두 가지 방법은 근본적으로 차이가 없다. 단지 이렇게 Static Typing 을 사용하여 코드를 작성하면 컴파일러에게 관련 코드에서 검사해서 경고 메시지를 출력할 것을 요청하는 것이라고 보면 된다. 자세한 사항은 Static Typing 에 대한 자료를 참조하라.

프로그램을 작성하다보면 수 많은 클래스를 사용하고, 스스로도 많은 클래스를 만들어서 사용하게 된다. 그런데 모든 객체는 반드시 공통적으로 가져야 하는 기능이 상당히 많이 있다. 인스턴스 생성을 위해서 메모리를 할당하거나, 초기화 하는 과정, 그리고 메모리를 반환하는 것 부터 시작해서, 상속 관계 정보를 알려주는 각종 메소드 등이 필수적으로 있어야 한다.
이렇게 많은 기능을 공유하기 위해서, 당연히 객체 지향 프로그래밍에서는 공통의 Root 클래스를 사용한다. Mac OS 에서는 가장 기본적인 프레임워크에 
NSObject 라는 클래스가 있다. 이것은 모든 클래스의 Super 클래스이며, 원조 대왕마마 클래스다. 단도직입적으로 모든 클래스는 NSObject 로 부터 시작된다.

NSObject 에서 기본적인 기능들을 모두 제공하기 때문에, 클래스는 다음과 같이 인스턴스를 생성할 수 있다.

id myRect;
myRect = [Rectangle alloc];

혹은, init 메시지 전달로 초기화 과정까지 함께 하는 방법을 많이 사용한다.

myRect = [[Rectangle alloc] init];

클래스를 선언하면, 하나의 헤더 파일로 인터페이스를 만들고, 같은 이름으로 클래스 소스 코드 파일을 만드는 것이 기본이다. 인터페이스의 형태는 아래와 같다.

@interface 클래스이름 : 수퍼클래스이름{
    인스턴스 변수 선언
}
메소드 선언
@end

중괄호 {} 안에는 클래스의 인스턴스 변수 선언이 있는데, 메소드는 중괄호 밖에 명시한다는 점을 기억하자.
메 소드 선언에서 구분해야 할 것은, 인스턴스 메소드인가 클래스 메소드인가 하는 것이다. 메소드 선언 맨 앞에 있던 정체 불명의 마이너스 기호(-) 가 이것에 대한 것이다. 인스턴스 메소드(instance method)인 경우 메소드 앞에는 마이너스 기호를 붙인다.

- (void)setXPos:(float)x yPos:(float)y;

클래스 메소드(class method)라면, - 대신 + 기호를 붙여야 한다. 클래스 메소드는 코드 중복을 막을 수 있는 좋은 방법이다. 나중에 클래스메소드가 필요하다고 생각되는 경우가 생긴다면, 여러 가지 사항을 세심하게 고려해서 작성해야 한다.

이렇게 작상된 interface 헤더 파일은 #import 를 사용해서 #include 를 쓰는 것과 같이 소스 코드에 사용할 수 있다. 만일 #import 를 쓰는 대신 일부 클래스 이름만 명시해서 컴파일이 진행되도록 하고자 한다면, 다음과 같이 할 수도 있다.

@class  Rectangle, myClass;

이렇게 하면, 컴파일러는 해당 이름의 기호가 단순히 어떤 객체가 될 것임을 알고서 컴파일을 진행하게 된다. 이 방법은 아직 작성되지도 않은 객체에 대한 작업이 가능하게 한다. C 언어에서 extern 선언을 하도 그 실체가 없다면 Link 에러가 나는 것에 반해, Objective-C 는 보다 더 추상화 되어 있는 셈이다.

interface 파일과 짝을 이루는 클래스의 실제 내용은 implementation 파일이라고 부른다. 클래스의 implementation 의 형식도 interface 와 유사한 구조를 가진다.

@implementation 클래스이름 : 수퍼클래스이름{
    인스턴스 변수 선언
}
메소드 선언
@end


메소드를 선언하면서 @implementation 안에서 메소드의 실제 구현 코드가 있어야 한다. 당연히 맨 마지막 열에 있던 세미콜론 문자 대신 중괄호 {} 안에 구현 코드를 작성하면 된다.

3.5. Property


property 는 예전 Objective-C 에서는 없던 내용으로, 언어 규약이 새로 확장되면 추가된 내용이다. 새로 등장한 것이면서도 워낙 많이 등장하기 때문에 꼭 살펴보고 넘어가야 한다.

기본적으로, 객체가 가지고 있는 하나의 인스턴스 변수에 읽고 쓰는 접근을 하기 위해서, 객체 외부에서 변수 값을 읽거나 쓸 수 있는 메소드를 통하는 형식을 취해야 한다. 이런 목적의 메소드를 
accessor 메소드라고 부르고, 각각 getter / setter 메소드라고 칭한다.

Property 의 목적은 이 accessor 메소드를 쉽게 사용할 수 있도록 도와주는 것이다.

예를 들면, value 라는 인스턴스 변수를 위한 accessor 메소드를 모두 만든다면 interface 코드와  implementation 코드는 아래와 비슷해진다.

@interface MyClass : NSObject
{
    int value;
}
-(int)value;
-(void)setValue:(int)newValue;
@end


@implementation MyClass

- (int)value {
    return value;
}

- (void)setValue: (int)newValue {
    value = newValue;
}
@end


이런 식으로 된 코드를 property 를 사용하면 다음과 유사한 형태가 된다.

@interface MyClass : NSObject
{
    int value;
}
@property (copy, readwrite) int value;
@end


@implementation MyClass
@synthesize value;
@end

property 와 관련된 지시자(directive) 키워드는 @property, @synthesize, @dynamic 등이 있고, @property 를 선언할 때 괄호 안에 속성을 지정할 수 있다. property 를 사용해서 객체 지향 프로그래밍의 기본 질서를 잘 지키면서 보다 편리하게 프로그래밍을 할 수 있다.

한가지 더 기억할 점이 있는데, 다음과 같이 전통적인 dot 문법을 사용하는 경우다. 물론 이렇게 사용해도 틀리지 않다.

myClassInstance.value = 30;
NSLog(@"my value: %@", myClassInstance.value );

그런데, 이것은 value 라는 인스턴스 변수에 직접 접근한 것이 아니다. 컴파일러가 이런 dot 표현식을 만나면, 이것은 accessor 를 사용한 표현식과 동일하게 취급된다. 즉, 위의 예제는 아래 코드와 완전히 같다.

[myClassInstance setValue:30];
NSLog(@"my value:%@", [myClassInstance value] );

C 와 C++, Java 등 기존의 대중적인 프로그래밍 언어에 친숙한 사람들을 위한 배려라고 할 수도 있고, C 언어에서 확장된 문법 형태를 가지면서 자연스럽게 일관성이 유지된 것이라고 생각할 수도 있다. 문법적으로 동일한 의미의 코드라는 점은 꼭 기억해 두어야 하겠다.
3.6. Fast Enumeration

Objective-C 2.0 에서 부터는 프로그래머의 편의를 위한 기능이 추가된 것이 있다. enum 을 보다 편리하게 사용할 수 있도록 해주는 기능이라고 보면된다. 이것을 Fast Enumeration 이라고 부른다.

예를 들면, 다음과 같다.

NSArray *array = [NSArray arrayWithObjects: @"one", @"two", @"three", nil ];for( NSString *emt in array ){
    NSLog(@"element: %@", emt );
}

여기서는 NSArray 를 사용하고 있지만, NSDictionary 를 사용하는 것도 상당히 자주 사용되는 방법이다. 또한, Fast Enumeration 을 위해서 NSEnumerator 객체를 사용할 수도 있다. (각 객체에 대한 사항은 레퍼런스 문서를 참조하라)
NSEnumeration 객체는 참조 순서를 변경하기 위해 주로 사용된다. 다음의 코드를 살펴보면 NSEnumeartion 객체를 사용하는 경우를 상상할 수 있다.

NSArray *array = [NSArray arrayWithObjects: @"One", @"Two", @"Three", @"Four", nil];

NSEnumerator *enumerator = [array reverseObjectEnumerator];
for( NSString *element in enumerator) {
    if( [element isEqualToString:@"Three"] )
        break;
}
NSString *next = [enumerator nextObject];

보통 가독성 높은 프로그램 코드를 작성하기 위해 C 프로그래머들도 enum 을 많이 사용하는 것을 볼 수 있는데, Objective-C 를 사용한다면 Fast Enumeration 을 사용해서 손쉽게 보기 좋은 코드를 작성할 수 있을 것이다.
3.7. Protocol

프로토콜은, Java 로 치자면 인터페이스와 같다. Objective-C 역시 클래스의 다중 상속은 불가능하다. 따라서 프로토콜을 통해서 다중 상속과 유사한 구현을 해야 한다.

이미 본 것과 같이 @interface 선언에서 꺽쇠괄호로 지정해 주는 것이 프로토콜의 이름이다.

@interface 클래스명 : 수퍼클래스명 <프로토콜(, 프로토콜,...)>
{
    // ...
}
// ...
@end

프로토콜은, @protocol 지시문으로 정의된다. 프로토콜도 클래스처럼 다른 프로토콜을 계승해서 정의하는 것도 가능하다. 프로토콜의 내용은 당연히 하나 이상의 메소드들의 집합이다.
예를 들면, 다음과 같은 방식으로 정의될 수 있다.

@protocol MyXMLSupport
- (NSXMLElement *)XMLRepresentation;
- initFromXMLRepresentation:(NSXMLElement *)XMLElement;
@end

프로토콜을 사용할 경우 #import 문으로 프로토콜이 정의된 헤더 파일을 포함할 수도 있고, @protocol 지시문을 사용해도 된다. 즉;

#import 프로토콜 파일명
혹은
@protocol 프로토콜명;

프로토콜로 만들어진 객체는 Protocol 이라는 이름의 클래스다. 또한 @protocol(이름) 형식을 사용해서 해당 프로토콜의 클래스를 얻을 수 있다.

Protocol *myXMLSupportProtocol = @protocol(MyXMLSupport);

프로토콜이라는 것은 아주 단순하게 생각하면 메소드 선언일 뿐이다. 결국, 다른 클래스에 있는 메소드를 선언하는것 그 자체가 프로토콜인 셈이다.
그래서, @protocol 지시문을 사용하는 경우는 
Formal Protocol 이라고 말하고, 반대로 단순하게 메소드 선언만 사용하는 것을 Informal Protocol 이라고 한다. 우리 말로 생각하면 "약식 프로토콜" 이랄까? "간이 프로토콜" 이라고 부르는 곳도 있다.
약식 프로토콜은 메소드가 선언만 되어 있을 뿐 그 구현은 없는 것이기 때문에, 해당 메소드를 구현할 클래스는 스스로의 인터페이스에 해당 메소드를 선언한 후 구현파일에 메소드의 동작 내용을 정의한다.

각자 프로토콜에 대해서 Objective-C 문서를 참조하여 보다 자세한 내용을 살펴보도록 하자.

3.8. 마무리

쓰다보니 Objective-C 언어에 대해 간략하게 다루더라도 체계적으로 정리한다는 것이 상당히 시간이 오래 걸리는 일이 될 것이라는 것을 알게 되었다. 그래서 이번 문서는 아주 짧은 메모 요약본 정도의 수준밖에 되지 않은 것 같다. 여기서 언급하지도 않은 내용도 상당히 많기 때문에, 오히려 도움이 되지 않을지도 모르겠다. 그래도 예제 프로그램을 살펴보고 작성하는데에 도움이 될 수 있을 정도로 눈에 익히는 것을 목표로 하였다.
Objective-C 2.0 언어에 대해서는 따로 KLDP Wiki 페이지에서 상세한 정리 문서를 작서하도록 하겠다. 부디 3장에서 살펴본 내용이 Objective-C 언어에 대한 감이라도 대충 잡는데 도움이 되었으면 하고 희망해본다.

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++ 언어보다 훨씬 쉽다.

iphone programming : view 구성하기

2.7. IB 에서 View 구성하기

지금까지 우리는 정해진 템플릿에 의해 Xcode 가 기본으로 생성해준 코드들과 XIB 내용을 살펴보고, 프로그램이 시작되는 동작을 살짝 엿보았다.

이제 Interface Builder 에서 새로운 UI 를 작성해 보자. 처음에는 하나의 Label 아이템만을 사용했지만, 이제는 3가지 아이템을 View 창 위로 끌어다 놓고, 위치와 속성을 조절할 것이다.

우리가 사용해볼 아이템은 Text Field, Label, 그리고 Round Rect Button 이 되겠다. 필자는 아래 그림처럼 세 아이템을 나열하고, 크기와 위치를 조정한 후, 몇가지 속성을 수정하였다. 기본적으로 이것은 Apple 의 iPhone Application Tutorial 문서에서 보여주는 예제와 같다.


여러분은 각자 원하는대로 조금씩 그 형태를 다르게 해도 무방하다. 참고로, 위 그림에서 버튼의 색이 파랗게 보이는 이유는 필자가 버튼 속성에서 Highlighted 항목을 체크 표시로 변경했기 때문이다.

각각의 UI 아이템을 배치하고 속성을 조절하는 것에 대해서 더 이상 설명할 필요는 없을 것으로 생각된다. 대부분 이런 식으로 UI 를 제작하는 프로그래밍 툴을 한두가지 사용해 본 경험이 있을 것이고, 근본적으로는 이런 식의 UI 디자인 개발 도구 자체가 직관적으로 사용할 수 있도록 되어 있기 때문이다.
게다가, 이건 다른 회사도 아닌 Apple 에서 만든 (NeXT STEP 부터의 역사를 가진) 개발 도구란 말이다. 직관적으로 빠르게 학습 가능한 UI 로는 세계 최고의 회사가 만든 도구라는 것을 상기하자.


이제, 우리가 원하는 프로그램의 외형은 일단 완성되었다. 성급하지만, 이 상태에서 Interface Builder 의 XIB 파일을 저장하고, Xcode 로 전환한 후 Build & Run 을 수행해 보자. 시뮬레이터가 동작하면서 지금까지 작성한 결과를 보여준다.

인터페이스 빌더로 작성한대로 모든 것이 보여질 것이다. 물론, 버튼을 눌러도 아무 동작도 하지 않는다. 그렇다면, 이번에는 맨 위에 배치한 텍스트 필드를 클릭해보자.
텍스트 필드에 클릭을 했다는 것은 당연히 사용자가 원하는 문자를 입력하려는 것이다. 따라서, iPhone OS 는 사용자가 문자를 입력할 수 있도록 키보드를 제공한다. 우리는 텍스트 필드를 화면에 배치하면서 여기에 사용할 키보드 형태를 별도로 변경하지 않았기 때문에, 화면에 기본형 키보드가 나타날 것이다. 만일 시뮬레이터 환경 설정에서 다국어 입력 지원을 설정해 놓았다면, 키보드의 지구본 키를 클릭히여 한국어를 비롯한 다른 언어들의 키보드로 전환할 수도 있다.

키보드 버튼들을 클릭하면, 입력된 문자가 텍스트 필드에 입력된다. 그런데, Enter 키를 클릭해도 아무런 변화가 없다. 키보드 입력 상태를 벗어나기 위해서 화면 밖 부분을 클릭하거나 다른 시도를 아무리 해봐도, 키보드를 다시 닫고 처음 화면으로 돌아갈 수 없을 것이다!

그 이유는, 필요한 키보드 입력 처리 코드를 작성하지 않았기 때문이다. Enter 키 입력에 대한 처리를 추가하면 의도한대로 다시 키보드를 닫을 수가 있게 될 것이다. 하지만 현재 상태로는 Home 버튼을 눌러서 프로그램을 종료한 후 다시 실행하는 방법 밖에는 없다. 실망스럽겠지만, 온전한 동작을 하도록 코드를 구성하는 것은 조금 후에 작업하도록 하겠다.

그러면 시뮬레이터를 닫아서 구경은 그만두고, 다시 Interface Builder 로 돌아가 보도록 하자.


자, 다시 현재 상황을 보자.
IB 에서 구성한 사용자 인터페이스는 시뮬레이터에서 확인해 본 대로, 정확히 눈에 보이는 것 그대로 구성되어 있다. 하지만 서로 연결되어 동작하는 것은 하나도 없는 상태다.

각 UI 아이템들이 서로 유기적인 동작을 이루기 위해서는, 먼저 
'연결'이 되어야 한다. 따라서, 구체적인 코드를 작성하기 전해 해야 하는 작업은 이 아이템들을 연결하는 일이다.
구슬이 서말 이라도 꿰어야 보배이듯이, View 위에 아이템이 서말이라도 연결해야 보배다.

사실, 연결 작업 그 자체는 Objective-C 소스 코드에 코딩을 하는 작업이다. 그래서 지금 Interface Builder 를 닫고 Xocde Workspace 상에서 작업을 해도 된다. 실제 Apple 의 Tutorial 문서에서는 소스 코드에서 작업하는 것을 기본으로 설명하고 있다.

하지만, 여기에는 선택이 가능하다. 즉, 작업자의 기호에 따라 Workspace 에서 직접 소스코드를 작성해도 되며, 같은 작업을 Interface Builder 에서 해도 된다. 전적으로 이것은 프로그래머가 원하는 바에 달렸다.

이 문서에서는 이미 말한대로, Interface Builder 에서 작업하는 것을 중심으로 설명해 나가기로 한다. 작업 결과를 보면 자연스럽게 소스 코드를 어떻게 고치면 되는지 알 수 있고, 따라서 처음부터 소스코드에서 작업하는 것 보다는 IB 에서 하는 것이 기억하기 쉽다.



그러면, 짝짓기를 해보자.

Interface Builder 에서 작업했던 MyHellloViewController.xib 를 열고, 여기서 File's Owner 아이콘을 선택하면 Identity 창에 Class Actions 칸과 Class Outlets 창 등이 있는 것을 볼 수 있을 것이다.

지금부터 잘 보기 바란다.

먼저, 하나의 
Action 을 지정하자.
액션은 말 그대로, 어떤 동작을 할 것인지에 관한 것이다.
MyHelloViewController 에 Action 을 추가한다는 것은, 다른 외부 객체가 지금 우리가 생성하고 있는 이 객체로 어떤 동작(Action)을 요구할 수 있도록 연결점을 만든다는 뜻이다.

도데체 어떤 녀석이 이 컨트롤러에게 동작을 요구할 것인가? View 에서 이 Controller 에게 동작을 요구할 녀석이 누구일지 생각해 보자.
현재 View 위에 배치된 세 개의 아이템 중, '버튼' 만이 그런 요구를 할 녀석이다. 사용자가 버튼을 클릭하면 버튼은 controller 에게 '야, 나 눌렸어! 이제 알아서 해!" 라고 Action 을 요구해야 한다.

따라서, 우리의 MyHelloViewController 에는 버튼이 Action 을 요구할 수 있는 문이 있어야 한다.
그 작업이 바로 컨트롤러에 Action 을 추가하는 작업이다.

Interface Builder 상에서 Action 추가는, Identity 창에서 Class Actions 항목 아래쪽에 있는 플러스[+] 버튼을 누른 후 왼쪽 칸에 원하는 이름을 기입하면 된다. 지금 type 은 신경쓰지 말고 id 로 남겨둔다.

Action 의 이름은 콜론(:)으로 끝나는 문자열이다. 왜냐하면, 사실 Action 이라는 것의 개념은 다른 클래스에서 호출하는 메소드와 같다. 즉, 다른 누군가가 호출할 함수 하나를 만든 셈이다.




우리의 프로그램에서는 Action 은 하나로 충분하다. 이제, Identity 창의 아래쪽에서 
Outlet 을 추가하도록 한다.
Outlet 은 Action 과는 반대다.
Outlet 은, 컨트롤러 안에서 다른 객체를 지정해서 사용할 명칭을 의미한다.
다시 말해서, 미국에 있는 어떤 도시를 우리나라 사람이 '나성' 이라고 부르는 행위와 같다. 여기서 
'나성'이라는 단어는 우리나라가 사용하는 하나의 Outlet 이 되며, 이 Outlet 은 미국의 LA 와 연결된다.

비유가 좀 괴상한가? 말로 길게 설명하는 것이 오히려 도움이 안될지도 모르겠다.
일 단, 작업 진행을 계속 살펴보도록 하자. 그림에서 보는 것과 같이 Identity 창에서 두 개의 Outlet 을 추가하는데, label 이라는 이름의 아웃렛과 textField 라는 이름의 아웃렛이다. 이름은 아주 단순하게 지었다.
그리고 각 아웃렛의 우측 열에는 각 아웃렛이 연결될 Object 가 어떤 형태인지 그 클래스를 명시해 준다. 이렇게 위 그림과 같이 추가했다면 일단 Outlet 생성 작업은 끝이다.

다시 정리해보자. File's Owner 에 두 개의 Outlet 을 추가했다. 이 뜻은, MyHelloViewController 객체에서 label 이라는 이름으로 외부에 어떤 UILabel 형태의 객체를 참조해서 사용하겠다는 뜻이고, textField 라는 이름으로 외부의 어떤 UITextField 형태의 객체를 참조해서 사용하겠다는 뜻이다.

사용되어질 Action 도 만들고, 사용하겠다고 Outlet 까지 만들었는데, 누가 Action 을 사용해 줄 것이며, 누구의 객체를 Outlet 을 통해서 사용하겠다는 말인가?
컨트롤러 객체 안에서 혼자 주장해봤자 아무 소용 없다. 실제 우리가 목적으로 하는 형태로 실제 연결을 해 주면 진짜로 구슬 서 말이 꿰어서 보배가 된다.

IB 에서 연결 작업은 흥미롭다.
먼저, File's Owner 아이콘을 마우스 오른쪽 클릭 - 필자같이 아직도 원 버튼 마우스 사용자라면 Ctrl-클릭 - 을 한다. 그러면 반 투명으로 시커먼 목록 창이 하나 나타난다.

여기에는 컨트롤러 객체가 가지고 있는 아웃렛과 액션 등의 목록이 연결 상태와 함께 나타난다. 그중 우리가 앞에서 등록한 label 이라는 이름의 아웃렛이 보이는데, 여기서 우측에 있는 작은 동그라미를 클릭한 후 화면 밖으로 쭉 드래깅 해 보자!

파란 직선이 그어지는데, 이 선을 View 창에서 실제 Label 아이템 위에다가 놓고 마우스 버튼을 놓으면 해당 아웃렛과 지정한 라벨 객체의 연결이 완성된다. 아주 직관적인 작업이 아닐 수 없다.
설명이 조금 와닿지 않는다면, 아래 그림을 참조해서 잘 보기 바란다.


같은 방법으로 TextField 아웃렛도 연결해 보자. 잘 살펴보면, view 라는 이름의 아웃렛이 이미 View 와 연결되어서 등록되어 있는 것을 볼 수 있다. 이렇게 연결 작업이 끝난 label 아웃렛과 textField 아웃렛은 Controller 코드 안에서 원하는 목적에 맞게 사용할 수 있을 것이다.

이제 Outlet 을 준비하는 과정은 끝났다. 이제 남은 Action 의 연결을 완성하는 것으로 준비 과정을 마무리 짓기로 하자. Action 의 연결 작업도 Outlet 과 별로 다른점은 없다.

앞서 했던 방식대로, File's Owner 아이콘에서 마우스 우측 클릭으로 연결상태 창을 연 후에 이번에는 changeGreeting: 아웃렛의 우측 동그라미를 버튼과 연결한다.버튼 위레서 바우를 떼면, 버튼의 어떤 동작과 해당 Action 을 연결할 것인지 샌택하는 또 하나의 반투명 팝업 창이 나타난다. 여기서 가장 일반적으로 버튼 동작에 사용하는 
Touch Up Inside 와 연결하면 된다.

참고로, 반대로 작업할 수도 있다. View 의 버튼 아이템에서 마우스 우측 클릭을 하면 마찬가지로 오른쪽에 동그라미를 달고 있는 항목들로 이루어진 반투명 창이 나타나는데, 여기서 Touch Up Inside 의 오른쪽에 있는 동그라미에서 마우스를 클릭한 후 File's Owner 아이콘으로 끌고가면 된다. 마우스를 놓으면 어느 Action 과 연결할 것인지를 선택하는 창이 나타나는데, 우리가 만들어놓은 Action 은 하나 뿐이므로, changeGreetion: 하나만 선택할 수 있는 작은 창으로 나타날 것이다. 이것을 선택해 주면 역시 Action 의 연결 작업이 완결된다.


이렇게 GUI 객체간의 연결 작업을 마우스로 선을 그어 연결하는 방식은 기본적으로 NeXT STEP 의 프로그래밍 환경에서 하던 것과 별 차이가 없다. 현재의 Xcode 는 NeXT STEP 의 개발 환경이 아직도 진화중인 모습에 불과하다고 말할 수 있다.

자, 이제 정신을 차려보자. 어느덧 Interface Builder 에서 구성할 수 있는 모든 작업이 다 끝난 셈이다. 지금 우리가 뭘 했는지 다시 한마디로 말해보자면, Interface Builder 를 이용해서 View 를 구성했고, 이와 연결된 Controller 의 기본 형태를 작성했다.
MVC 중에서 View 와 Controller 일부를 작성한 셈이다.

Controller 를 작성했다고?
그렇다. 분명히 화면에 버튼과 라벨을 배치한 View 와 연결된, 그것의 컨트롤러인 MyHelloViewController 에 아웃렛과 액션을 정의해서 연결을 만들었다. 구체적인 동작은 없지만 Controller 의 기본 틀은 모두 작성한 것이다.

하지만, 당연히 Controller 라는 것은 Objective-C 소스 코드이어야 한다. 그래야 구체적인 동작을 작성해서 프로그램을 완성할 수 있으니까.
그렇다면, 지금까지 Interface Builder 에서 작성한 내용도 소스 코드로 작성되어 있어야 한다. 물론, 처음 템플릿에 의해 MyHelloViewController.m 과 MyHelloViewController.h 헤더 파일이 작성되어 있다. 그러나, 우리가 지금 추가로 Interface Builder 에서 작업한 내용은 아직 소스 코드에 반영되지 않은 상태다.

그래서, Outlet 과 Action 작성, 연결 등을 한 내용을 컨트롤러 소스 코드에 반영해주는 작업이 필요하다.
아래 그림처럼 Interface Builder 파일 메뉴를 보자. 여기서 
Write Class Files... 항목을 선택하면 작업한 내용을 소스 코드로 작성해서 저장하게 된다.(잠깐. 작성된 View 를 저장하기 위해서는 XIB 파일을 저장해야 하는 것은 당연하다. 혼동하지 말자.)



저장하는 파일의 이름과 위치, 그리고 헤더 파일을 함께 저장할지 등을 묻는 대화창이 다음 그림과 같이 나타난다. 일반적으로 별도의 변경 없이 그대로 Save 버튼을 클릭해서 진행하면 된다. 앞서 언급한 바와 같이, MyHelloViewController.m 과 .h 는 템플릿에 의해 이미 작성되어 있기 때문에, 저장할 위치에 같은 이름의 파일이 보일 것이다.


같은 이름의 파일이 이미 있으므로, Save 를 진행하면 당연히 다음과 같은 창이 나타난다. 소스 머지를 선택하는 것이 일반적인 선택이 될 것이다. 혹은 기존 소스를 새로운 소스로 완전히 대체할 수도 있다. 상황에 맞게 선택하면 된다.




Interface Builder 안에서 할 수 있는 일들은 다 한것 같다. 이제 Xcode 의 Workspace 로 돌아와서, 방금 저장한 MyHelloViewController.m 과 MyHelloViewController.h 파일의 내용을 확인해 보자. 분명히 새로 추가된 내용들이 보일 것이고, 그것은 우리가 만든 Outlet 과 Action 에 대한 코드일 것이다.

Interface Builder 에서 Outlet 과 Action 에 대해서 작업한 내용이 결과적으로 Controller 소스 코드에 저장되는 것이라면, 그 반대의 작업도 가능하지 않을까?
다시말해서, 처음부터 Workspace 에서 MyHelloViewController.m/h 소스코드에 직접 코딩해서 작업하면 되지 않을까 하는 것이다.

물론 가능하다. 조금 익숙해진다면 각자 기호에 따라서 처음부터 코드로 작성하는 것이 편한 사람들도 있을 것이다. 그러나 처음에는 Interface Builer 에서 작업하는 것이 보다 직관적인 것은 사실이다.

한가지 주의할 것이 있는데, 처음부터 소스 코드에서 Outlet 과 Action 을 만들어서 연결하는 작업을 했다면, 반대로 나중에 Interface Builder 를 다시 실행해서 해당 View 의 XIB 내요을 수정하려 한다면, 작성된 컨트롤러 클래스 내용을 IB 로 반영해야 작업에 실수가 없게 된다.

메뉴에서 
Read Class Files... 를 선택하면 아까 Write Class Files... 를 했던 것과는 반대로 클래스 소스 코드에 있는 내용이 Interface Builder 에 반영된다. Workspace 와 IB 사이를 왔다갔다 작업할 때는 꼭 이 점을 잊지말아야 사고를 막을 수 있을 것이다.


별것도 아닌 내용을 너무 장황하게 설명한 것은 아닌가 걱정된다. 그러나 Interface Builder 는 GUI 개발 도구 중에서 가장 쉬운, 그리고 MVC 모델이 완벽하게 실체화 된 개발 도구가 아닐까 감히 생각해본다. 따라서 처음 맥 환경에서 프로그래밍을 시도하려는 사람이라도 조금만 살펴보면 적응할 수 있으리라 생각된다.

또 한가지 명심할 것은, 지금까지 살펴본 것 처럼 View 를 구성하고 Controller 클래스를 만들고, Outlet 과 Action 을 작성하여 연결하는 등의 작업은 Mac Desktop 용 application 을 작성할 때에도 방법이 완전히 동일하다는 것이다. 차이가 있다면 오직 사용하는 UI 아이템들 뿐이다. 따라서, iPhone 프로그래밍 만을 하기 위해 Interface Builder 사용법을 익혔다 해도, 이미 절반 이상은 Mac OS 프로그래머가 된 셈이다.

반대로 생각하면, 기존에 Mac OS 에서 데스크탑용 프로그램을 Xcode 를 사용해서 작성할 줄 알던 사람이라면, iPhone 응용 프로그램은 아주 쉽게 작성이 가능한 것이다.
이 점이 전 세계를 놓고 봤을 때 무료로 배포된 iPhone SDK 를 통해서 곧 수 많은 응용 프로그램이 등장하리라는 예상이 가능한 요인 중 하나다.

맥 프로그래머가 극히 적은 국내에서는, 이 문서와 같은 기초 입문 문서가 많은 이들에게 도움을 줄 수 있으리라 감히 기대해본다.


이제 Controller 클래스가 제구실을 할 수 있도록 만들어보자.

iphone programming : Design patterns

2.5. Design Patterns

객체 지향 프로그래밍(Object-Oriented Programming)을 하는 사람들이 항상 고려하는 것 중 하나가 바로 디자인 패턴(Design Pattern) 이라는 것이다. 이 글을 읽는 분들 중에서도 이것에 대해 이미 잘 알고 있는 사람도 많을 것으로 생각된다. 디자인 패턴은 개념적인 것으로서, 프로그램의 구조를 보다 큰 관점에서 보다 유지/보수 관리를 하기 쉽게 하고, 효율성을 높이기 위한 것이다. 디자인 패턴에 대해서는 풍부한 각종 자료를 쉽게 찾아볼 수 있을 것이다. 
Cocoa Fundamentals Guide 문서에서 디자인 패턴에 대한 부분을 참고해도 좋다.

iPhone SDK 위에서 응용 프로그램을 작성하기 위해서는 다음 세 개의 개념이 중심이 된다.
Delegation



우리 말로 '위임' 이라고 말하는데, 대다수의 프로그래밍 용어들이 그렇듯이 이것도 번역한 용어는 어딘가 어색한 느낌을 지울 수 없다. 각설하고, Delegation 은 iPhone SDK 프로그래밍에서 아주 많이 등장하는 패턴이다.
하 나의 객체가 지정된 다른 객체로 주기적으로 메시지를 보내서, 해당 메시지를 처리할 수 있다면 처리할 것을 요구하는 형태를 말한다. 또는, 특정 이벤트가 발생한 경우 그 이벤트를 지정된 다른 객체로 전달하여 처리할 것을 요구하는 것을 말한다.
사실, 프로그램을 작성하는 입장에서 이것은 단순하다. 전달받는 - 혹은 전달하는 - 이벤트일 뿐이다.
우 리가 위임 이벤트를 만나는 것은 Application 이 실행되는 첫 순간부터다. Application 객체는 초기 시작 처리가 다 끝나고나서 applicationDidFinishLaunching: 이라는 이름의 이벤트를 지정된 객체로 전달한다. 첫 시작부터 Delegation 을 만날 수 밖에 없는 것이 iPhone SDK 프로그래밍이다.

Model View Controller



디자인 패턴에 대해 잘 알지 못하는 사람이라도 MVC 에 대해서는 알고 있을 것이다. iPhone SDK 프로그래밍은 MVC 디자인 패턴에 기초한다.
간 단히 짚어보자면, Model 객체는 프로그램이 사용하는 데이터를 의미하며, View 객체는 데이터를 화면에 표시하는 방법이나 사용자가 데이터를 수정하는 방법을 가지고 있는 객체를 의미한다. Interface Builder 는 이 View 를 손쉽게 작성할 수 있도록 도와준다. Controller 객체는 Model 과 View 사이에서 각종 처리를 담당하고 둘 사이를 연결하는 것이다. 쉽게 생각해서 우리가 프로그래밍 언어로 작성하는 프로그램 코드의 대부분은 Controller 가 된다.

Target - Action



만일 화면에 있는 버튼을 손가락으로 누른다면, 이벤트가 다른 오브젝트로 전달된다. 그 이벤트를 받은 오브젝트는 이벤트를 해석하고 그에 알맞는 명령을 수행하게 된다. 이 단순한 메카니즘을 Target-Action 패턴이라고 한다. 단순하지만 앞서 예로 들은 버튼의 경우처럼, 대부분의 iPhone 프로그래밍에서 항상 등장한다.

위의 세 가지 용어는 iPhone SDK 프로그래밍에서는 상식과도 같은 개념이 된다. 모든 것이 세 가지 디자인 패턴 속에서 이루어진다.


2.6. 프로젝트 생성과 기본 코드.

다시 Xcode 를 실행해서, 새로운 프로그램을 작성해 보자. 완전히 새로운 프로젝트를 생성해서 시작해도 좋지만, 앞에서 실행해 본 Hello, iPhone 프로그램을 수정해도 상관 없다. 새로운 프로젝트를 만든다면 마찬가지로 View Based 템플릿을 사용해서 시작하도록 한다.

만일 기본적인 View 조차 없는 프로젝트라면, View 를 생성해서 프로젝트에 포함시키는 작업을 우리가 직접 해주어야 한다. 그러나 하나의 View 가 처음부터 포함되어 있는 템플릿 덕분에 많은 시간을 절약할 수 있다.

필자는 
MyHello 라는 이름의 새로운 프로젝트를 만들었다. 앞으로 편의상 이 이름을 기준으로 각 파일들의 이름을 부르도록 하겠다.

현재, 프로젝트에는 두 개의 XIB 파일이 있다. 여러분이 나중에 각종 프로그램을 작성하다보면 하나의 프로젝트에 여러 개의 XIB 파일이 포함될 것이다.
그렇다면, 프로그램이 처음 실행될 때 어떤 XIB 파일이 처음 사용되는 것일까?

고정된 이름을 가진 XIB 파일이 처음에 사용된다는 식의 원시적인 규칙은 아니다. Workspace 에서 다음 그림과 같이 Resources 항목에 등록된 파일들을 살펴보면, 그곳에 
info.plist 라는 파일이 있다. 이 파일을 선택하면, workspace 에서 그 내용을 보여준다.



info.plist 의 내용에는 작성하는 응용 프로그램의 기본 설정 사항이 저장되어 있으며, 그림에서 보면 아래쪽 항목에 처음 참조하는 XIB 파일이 지정되어 있는 'Main nib file base name'항목도 있다는 것을 볼 수 있다.
즉, iPhone 응용 프로그램이 실행될 때 info.plist 에 있는 각종 초기 설정에 따라 실행 준비가 이루어지며, 처음 사용할 XIB 파일을 선택하는 것도 여기에 있는 설정 값에 따라서 동작하는 것이다.
(여기서도 아직 nib 라는 용어가 xib 로 변경되지 않았다. 점차 모든 용어를 xib로 변경하는 중이니까, 이것도 
나중에 나올 버전의 Xcode 에서는 변경될 것으로 생각된다)

프로젝트가 저장되어 있는 폴더를 Fiinder 에서 직접 열어보면 info.plist 파일이 저장되어 있는 것을 쉽게 찾을 수 있다. 직접 더블 클릭하면 Property List Editor 가 실행되어 Xocde 의 Workspace 에서 보여준 것과 같은 내용을 볼 수 있다. 물로 양쪽 모두 수정도 가능하다. 강제로 텍스트 편집기 같은 에디터에서 이 파일을 열어보면, 단순한 xml 문서 파일임을 알 수 있다.

자, 별로 어렵지 않을 것이다. info.plist 의 자세한 분석은 그리 중요한 것이 아니다. 이제, 프로그램이 실행되면서 property list 에 지정된 XIB 파일이 읽혀진다는 것을 알았다.
그러면, 그 다음 어떤 일이 일어나는가?

MainWindow.xib 항목을 더블 클릭하여, Interface Builder 를 실행해 보자.

IB의 창을 보면, 이전에 보았던 View 의 XIB 파일과는 어딘가 다르게 보인다. IB 의 창에는 아이콘도 두 개 정도 더 있는 것 같고, View 창의 모습도 다르다.




View 창이 재미있다. 실제 iPhone 의 배터리 상태 및 통신 상태 표시칸도 보인다. 그리고 우측 상단에는 작은 휘어진 화살표가 있는데, 이것을 클릭하면 View 창이 실제로 90도 회전한다. 처음 직접 클릭해보면 기대 이상의 동작에 깜짝 놀랄 것이다.

지금 살펴볼 것은 MainWindow.xib 창에 있는 객체 아이콘들이다. 여기에는 처음 보는 아이콘들이 있는데, 그 중에서 MyHello App Delegate 라는 객체가 보인다. 이것을 선택한 후 속성 창에서 connections 탭을 선택해서 그 항목들을 살펴보자.



Referencing Outlets 로 하나의 Outlet 이 연결되어 있는데, delegate 가 File's Owner 와 연결되어 있다.(Outlet 이 뭔지는 나중에 다루자) File's Owner 객체를 선택해서 connections 를 살펴보면, 반대로 delegate 가 MyHello App Delegate 객체와 연결되어 있다는 것을 확인할 수 있다.

앞에서 말한대로, File's Owner 는 Application 자체의 인스턴스 객체를 의미한다. 여기에 delegate 클래스로 MyHello App Delegate 가 등록되어 있는 것이다. 고맙게도, MyHello App Delegate 클래스를 생성하고 그것을 File's Owner 의 delegate 메시지를 받도록 등록되어 있는 이유는 모두 템플릿이 기본적으로 자동 생성해 준 덕분이다.

이렇기 때문에, MyHelloAppDelegate.m 객체로 응용 프로그램의 delegate 이벤트가 전달된다.

이렇게 지정된 규칙에 따라, MyHello 프로그램은 처음 실행되면서 최초의 MainWindow.xib 에 연결된 delegate 연결을 보고서 '위임'메시지를 보내기 시작한다.

그렇게 처음 보내기 시작하는 delegate 메시지는 바로 
"나는 실행준비 다 끝났어. 너할거 있으면 해" 를 의미한다.
그 메시지의 이름은 
applicationDidFinishLaunching: 이다. Xocde 로 돌아와서, MyHelloAppDelegate.m 의 소스 코드를 살펴보자.
다음과 같은 코드가 생성되어 있는 것을 확인할 수 있을 것이다.



- (void)applicationDidFinishLaunching:(UIApplication *)application {    
    
    // Override point for customization after app launch    
    [window addSubview:viewController.view];
    [window makeKeyAndVisible];
}

이것은 Objective-C 프로그래밍 언어의 문법으로 되어 있는 소스 코드다. C++ 와 뭔가 유사성이 있어 보이기도 하는데, C 언어와는 좀 달라보인다.

이 문서에서는 Objetive-C 언어에 대한 상세한 설명은 하지 않는다. 개략적인 내용은 뒤에서 다시 정리하도록 하겠다. Objective-C 언어에 대한 설명은 애플 개발자 사이트에서 충분히 많은 문서를 찾을 수 있을 것이다.

간단히 설명하자면 applicationDidFinishLaunching: 메시지를 받아서 처리하는 코드를 작성한다는 것은, C 언어로 생각할때 applicationDidFinishLaunching: 이라는 이름의 함수를 만드는 것과 같은 말이다. 단, 메시지 전달은 함수 호출과는 달리 동적인 개념이며, 메시지를 받는 객체에서 해당 메시지를 처리할 수 있는 코드가 없다고 해서 컴파일 시 링크에러가 난다거나, 런타임에 에러가 발생하지는 않는다는 점만 명심하자.

위 코드의 내용을 보면, 윈도우에 하나의 view 를 추가하고나서, 그 윈도우를 화면에 표시하도록 해 주는 내용임을 알 수 있다.
이 런 식으로, MyHelloAppDelegate.m 에는 MyApp 프로그램의 Delegation 메시지를 처리하는 코드들이 있으며, 현재는 없지만 필요한 delegate 메시지 처리 코드를 추가할 수도 있다. 이와 함께 헤더 파일도 살펴보면서 코드의 스타일을 짐작해 보자.

프로그램이 종료될 때는 dealloc 메시지를 전달한다. 여기서는 메모리를 해제하는 코드들이 있게 된다. 이처럼 delegate 객체에서는 응용 프로그램이 위임하는 각종 이벤트를 받아서 처리하게 된다.



이제 기본적으로 초기화 과정에 대한 부분들을 살펴보았다. 이제 다른 부분을 한번 살펴보자. 계속 보다시피, 우리가 만든 프로젝트는 기본 템플릿에 의해 하나의 View 를 가지고 있으며, 그것은 이미 Window 화면에 연결되어 있는 상태다.

MVC 디자인 패턴을 떠올려보자. 여기 하나의 View 가 존재하므로, 있어야 할 것은 나머지 Model 과 Controller 이다.

앞서 말할 것 처럼 Model 은 프로그램이 처리할 자료를 말한다. 현재 우리가 작성할 프로그램은 별로 하는일이 없기 때문에 Model 에 대한 부분은 잊어버리자. 그러면 남는 것은 Controller 인데, 이것도 템플릿에 의해 MyHelloViewController.m 이라는 이름으로 해당 View 와 연결된 Controller 코드가 자동 생성되어 프로젝트에 포함되어 있는 것이다. 

MyHelloViewController.xib 를 더블 클릭하여 이것도 Interface Builder 에서 열어보자. Interface Builder 는 이미 열려있는 MainWindow.xib 파일과 동시에 보여줄 수 있다.




앞에서 해봤던 대로 xib 의 내용을 조금 살펴보자. 일단 주목할 것은, 여기에 있는 
File's Owner 는 응용 프로그램 자체가 아니라 Controller 의 인스턴스를 의미하는 객체다.
즉, MyHelloViewController.m 객체의 인스턴스를 의미하는 아이콘이다. 그리고 File's Owner 객체와 View 객체는 서로 View 컨트롤러의 
view Outlet(아웃렛) 에 의해 연결되어 있다. 마찬가지로 connections 창에서 서로의 연결을 확인할 수 있다.


outlet 은 단지 xib 파일에서 아이템을 서로 연결하는데에 사용되는 인스턴스 변수와 같은 것이다. MyHelloViewController 에 이런 인스턴스 변수가 있는 이유는, 모든 View 는 UIViewController 에서 상속되기 때문이다.


템플릿에 의해 제공된 MainWindow 와 View 는 이상 살펴본 형태로 서로 연결되어 구성되어 있다. 조금 더 살펴본다면 어느정도 감은 잡을 수 있을 것으로 생각된다. Objective-C 언어와 각종 용어의 낮설음은 일단 참고 그냥 넘어가보자.
지나보면 별거 아니다!