이번 장에서는 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];
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 );
NSLog(@"my value: %@", myClassInstance.value );
그런데, 이것은 value 라는 인스턴스 변수에 직접 접근한 것이 아니다. 컴파일러가 이런 dot 표현식을 만나면, 이것은 accessor 를 사용한 표현식과 동일하게 취급된다. 즉, 위의 예제는 아래 코드와 완전히 같다.
[myClassInstance setValue:30];
NSLog(@"my value:%@", [myClassInstance value] );
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 );
}
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];
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
{
// ...
}
// ...
@end
프로토콜은, @protocol 지시문으로 정의된다. 프로토콜도 클래스처럼 다른 프로토콜을 계승해서 정의하는 것도 가능하다. 프로토콜의 내용은 당연히 하나 이상의 메소드들의 집합이다.
예를 들면, 다음과 같은 방식으로 정의될 수 있다.
@protocol MyXMLSupport
- (NSXMLElement *)XMLRepresentation;
- initFromXMLRepresentation:(NSXMLElement *)XMLElement;
@end
- (NSXMLElement *)XMLRepresentation;
- initFromXMLRepresentation:(NSXMLElement *)XMLElement;
@end
프로토콜을 사용할 경우 #import 문으로 프로토콜이 정의된 헤더 파일을 포함할 수도 있고, @protocol 지시문을 사용해도 된다. 즉;
#import 프로토콜 파일명
혹은
@protocol 프로토콜명;
혹은
@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 언어에 대한 감이라도 대충 잡는데 도움이 되었으면 하고 희망해본다.