컴파일러
Objective-C는 객체 지향 언어이기 때문에, 재사용 가능한 데이터 및 메소드를 패키지 템플릿으로 제공하고 있습니다. 이것을 일반적으로 클래스(Class)라고 부르며, 클래스는 메모리에 실체를 생성하기 위한 정보(틀)입니다.
클래스의 개념은 C언어의 구조체를 발전시킨 것으로, 구조체와 같은 데이터 형식일 뿐만 아니라 데이터 및 제어(함수)를 가능하게 합니다. 따라서 코드는 항상 데이터를 처리하기 위한 전용 함수를 사용해서 안전하게 호출할 수 있고 다양한 기능을 하나의 시스템에서 제공할 수 있는 것입니다.
클래스를 이용하기 위해서는 구조체와 마찬가지로 먼저 선언해야 합니다. Objective-C의 클래스 선언은 예약어와 구문이 아닌 컴파일러 지시어를 사용합니다. 컴파일러는 Objective-C에서 확장된 클래스의 선언과 구현을 위한, @기호로 시작하는 구문들을 가지고 있습니다.
컴파일러에서 클래스를 선언하려면 @interface로 시작해서 @end로 끝냅니다. 이 구문 사이에 클래스와 관련된 변수 영역과 함수(메소드)를 선언합니다. Objective-C의 클래스 선언과 정의의 형태는 다른 언어에 비해서는 특이한 형태이기 때문에 처음에는 당황 스러울지도 모릅니다.
@interface 클래스명 : 부모 클래스 이름 <프로토콜>
{
인스턴스 변수 선언 ...
}
메서드 선언
@ end
위와 같이, C++ 계열의 객체 지향 언어와는 선언 방법이 매우 다르다는 것을 알 수 있습니다. 인스턴스 변수는 클래스와 관련된 변수이며 구조체의 멤버와 같은 존재입니다. 그러나 구조체와 달리 인스턴스 변수를 클래스 외부에서 액세스할 수 없습니다(Private). 인스턴스 변수를 이용하려면 원칙적으로 클래스에서 구현한 접근관련 메소드에서만 액세스 할 수 있도록 되어 있습니다. 인스턴스 변수가 없을 경우에는 인스턴스 변수 선언과 { } 를 생략할 수도 있습니다.
메서드는 클래스에 연관된 함수입니다. 일반 함수와 차이점은 메소드는 메소드를 호출하는 클래스의 객체에 연결되어있기 때문에 직접 인스턴스 변수에 액세스할 수 있습니다.
클래스 이름 뒤에 부모 클래스를 지정하게 되는데, 부모 클래스는 무엇일까요? 객체 지향은 한번 정의된 기능을 확장하는 상속이라는 기능을 제공합니다. 상속 관계는 생물의 진화론에 비유해서, 개나 고양이 등 다양한 실체에 공통되는 추상적인 기능들을 포유류라는 클래스로 정의하고, 개 클래스와 고양이 클래스는 포유류 클래스를 상속하도록 프로그램하는 방식입니다.
부모 클래스를 지정하지 않은 클래스는 기본적으로 루트 클래스를 상속합니다. Java 및.NET 플랫폼은 기본적으로 루트 클래스를 언어에서 제공하는 구조를 채택하고 있고, C++ 언어는 루트 클래스를 제공하지 않고 루트 클래스를 개발자가 직접 개발할 수 있도록 디자인되어 있습니다. Objective-C는 중간적인 형태로서 원칙적으로 루트 클래스를 개발할 수도 있지만, 실제로는 컴파일러에서 제공한 루트 클래스를 사용합니다.
루트 클래스는 컴파일러에 따라 다르지만 GCC의 경우 Object 클래스가 되고, Mac OS X의 Cocoa 환경이라면 NSObject 클래스가 루트 클래스입니다. 부모 클래스를 지정하지 않고 클래스를 선언하게 되면 기본적으로 루트클래스를 상속합니다. 루트 클래스를 상속하여 개발하는 경우, 선언은 다음과 같이 될 것입니다.
@ interface 클래스명
{
인스턴스 변수 선언 ...
}
메서드 선언
@ end
왜 루트 클래스를 상속해야만 하는가 하면, 루트 클래스는 클래스가 제대로 작동하기 위해서 필요한 기본적인 기능을 제공하는 역할을 하기 때문입니다. 루트 클래스가 존재하지 않는다면, 클래스의 실체를 만드는 등의 복잡한 절차를 개발자가 모든 클래스에서 직접 작성해야 합니다. 힙 메모리의 확보 및 해제 등의 기본적인 처리 절차는 루트 클래스에 맡겨 버림으로서, 개발자는 개발 목적의 기능을 구현하는 데 집중할 수 있습니다.
인스턴스 변수의 선언은 일반 변수와 비슷하지만 메서드 선언은 함수 선언과는 상당히 다릅니다. 메서드 선언의 방식이 C++에 비해 다른것이, Objective-C가 C언어 프로그래머들이 받아 들이기 힘들게 되는 요인 중에 하나라고 생각됩니다. 이러한 메서드 선언은 다음과 같습니다.
- (반환 형식) 메서드 이름 : 형식 인수 목록 ...;
왜 C++의 함수 선언과 이렇게 다를까요? 이것은 C언어와 C++언어의 하이브리드성 언어이기 때문이라고 생각할 수 있습니다. 첫 번째의 빼기기호 – 는 메소드가 인스턴스 메소드 라는 것을 나타냅니다. 일반적으로 메서드는 - 기호를 사용하지만, 객체가 아니라 클래스에 관련된 메소드를 선언하려면 + 기호를 지정합니다. - 기호 로 시작하는 메서드를 인스턴스 메서드라고 부르며, + 기호로 시작하는 메서드를 클래스 메서드라고 합니다. 인스턴스 메서드와 클래스 메서드의 차이는 나중에 자세히 설명 하겠지만, 일반적으로 –로 시작한다는 것을 기억하십시오.
반환 형식은 예제에서도 알 수 있듯이 메서드 선언에서 반환형식을 ()로 묶어야 합니다. 메서드가 인수를 받는 경우에는 콜론 : 다음에 형식 인수 목록을 지정합니다. 형식 인수 목록은 일반 변수 선언과 달리 변수의 형을 () 안에 지정하고 그에 따른 변수 이름을 지정하는 구조입니다.
C함수의 기본 반환값은 int 형식 이었지만, 메소드의 기본 반환값은 id 형식입니다. 반환 형식은 생략할 수 있지만, 일반적으로 의도적으로 생략해야 하는 경우가 아니라면 대부분 명시적으로 (void)를 지정합니다.
@ interface Test : Object
- (void) method;
@ end
이 Test 클래스의 선언은 리턴값을 반환하지 않고 인수를 받지 않는 method 메서드를 선언하는 간단한 클래스 선언의 예제입니다. 클래스 및 메서드 이름의 명명 규칙은 C언어의 변수와 구조체 명명 규칙과 마찬가지로 고유해야 하며 알파벳으로 시작해야 식별자가 될 수 있습니다. 그러나 관습적으로 클래스 이름은 대문자로 시작하고 메소드 이름은 소문자로 시작합니다.
그런데, 클래스와 메소드를 선언한다고 해서 실제 처리 코드를 작성하는 정의가 자동으로 생성되는 것은 아닙니다. 사실, Objective-C에서는 클래스 선언과 정의가 명확하게 분리되어 있으며, 클래스의 선언부에서는 인스턴스 변수와 메서드 선언 밖에 할 수 없습니다. 클래스 선언한 후 클래스 정의를 구현하려면 @implementation 컴파일러 지시자를 사용해서 클래스를 정의해야 합니다.
@implementation 클래스 이름
메서드 정의 ...
@ end
@implementation 지시자는 @interface에서 선언된 클래스를 구현합니다. 선언부에서 선언된 메서드는 이 부분에서 정의되어야 합니다. 클래스 선언부에서 메서드가 선언되지 않은 경우 @implementation 부분을 생략 할 수도 있지만, 정의된 클래스는 @implementation에서 명시적으로 구현해야 합니다.
클래스의 인스턴스화
클래스를 선언 및 정의하면 클래스를 이용할 수 있게 되지만, 그대로는 사용할 수 없습니다. 클래스를 사용하는 데 필요한 메모리를 할당하고 적절한 초기화를 해야만 클래스가 올바르게 작동합니다. 클래스의 선언 정보를 기반으로 메모리를 할당하는 것을 인스턴스화라고 부르며, 이렇게 확보된 메모리를 인스턴스 라고합니다. 인스턴스라고 부르는 경우 대개 클래스의 정보를 바탕으로 확보된 실체를 나타내지만, 또다른 이름으로 객체 라고도합니다.
C++ 언어와 Java 언어와 같은 일반적인 객체 지향 언어인 경우 new 연산자가 클래스 선언 정보를 기반으로 인스턴스를 생성하고 초기화 해줍니다. 그러나 Objective-C는 인스턴스의 생성 및 초기화를 클래스가 실행해야 한다고 규정하고 있습니다. 그런데 클래스를 인스턴스화 하려면 객체에 필요한 크기를 계산해서 메모리를 할당하는 등의 복잡한 초기화가 필요합니다. 이것을 모든 클래스에서 구현하는 것은 현실 적이지 않기 때문에 이 작업을 구현해 놓은 것이 루트 클래스입니다.
Object 클래스는 Object 클래스를 포함하여 그것을 상속하는 클래스의 인스턴스를 생성하기 위한 alloc 클래스 메소드를 정의하고 있습니다. 일반적인 인스턴스 메서드에서는 메서드를 호출하는 인스턴스가 필요하지만 클래스 메서드는 인스턴스 없이도 호출할 수 있다는 특징이 있습니다. 따라서 alloc 메소드는 인스턴스가 존재하지 않고서도 호출되어도 문제가 없습니다. alloc 메서드는 인스턴스를 생성하기 위한 클래스 메서드이므로 팩토리 메소드 라고도 합니다.
+alloc;
이것이 Object 클래스에서 선언된 alloc 메소드입니다. +로 시작하기 때문에 이 메서드는 클래스 메서드임을 나타내고 있습니다. 반환 형식은 생략되어 있기 때문에 기본적으로 id 형식이 반환됩니다.
id 형식은 객체를 나타내는 일반적인 형식, C언어의 void *와 같은 것이라고 생각하면 됩니다. 즉, 객체의 클래스 형식들은 모두 id 변수에 저장할 수 있습니다.
이제 클래스를 생성하기 위해 alloc 메소드를 호출해야 하지만, 정작 메소드는 어떻게 호출합니까? C++ 계열의 객체 지향 언어를 경험한 많은 프로그래머는 직관적으로 다음과 같은 코드를 상상할 것입니다.
id obj = 클래스 이름. alloc ();
불행히도, Objective-C는 많은 객체 지향 언어에서 사용되고 있는 이런 방식을 사용하지 않습니다. Objective-C에서는 클래스 메소드를 호출하려면 다음과 같이 작성해야 합니다.
[클래스 이름 메서드 이름 : 인수 목록 ...]
이것은 Smalltalk 언어를 객체 지향 부분으로 구현한 Objective-C만의 특징입니다. 인스턴스 메서드를 호출하려면 클래스 이름 부분을 인스턴스객체로 지정해야 니다. C언어에서는 []는 배열의 요소를 지정하는 데 사용되고 있었지만, 컴파일러는 [] 전후를 감지하여 메소드 호출인지 또는 배열 요소 지정인지를 확인할 수 있습니다 .
이렇게 메서드를 호출하기 위한 []형식을 메시지 식이라고 합니다. Objective-C에서는 메소드를 직접 호출하지 않고 객체에 대해 메서드와 관련된 메시지를 보내는 것입니다. 메시지식으로 객체에 메시지가 전송되면, 객체는 주어진 메시지에 따라 적절한 메서드를 호출하는 것입니다. 이러한 동적 구조는 유연성을 보장해 주고 있지만 C언어의 함수 호출에 비해 오버헤드가 두배 이상 소모된다고 생각하면 됩니다. 물론, 컴파일러 최적화와 같은 기능을 실행하면 오버헤드가 감소하기는 하지만 메시지의 동적 메서드 호출은 런타임에서 메서드를 검색하기 위한 오버헤드는 여전히 발생할 수 있습니다.
#import <stdio.h>
#import <objc/Object.h>
@interface Test : Object
- (void) method;
@end
@implementation Test
- (void) method
{
printf ( "YoonSeo on your lap \ n");
}
@end
int main()
{
id obj = [Test alloc];
[obj method];
return 0;
}
자, 이것이 간단한 Objective-C의 클래스를 구현하는 방법입니다. @interface가 클래스의 선언을 표시하며 루트 클래스 Object를 상속한 Test 클래스를 선언합니다. Test 클래스는 반환 값을 반환하지 않고 인수를 받지 않는 method 메서드를 선언합니다. 따라서 @implementation에서 이 메서드를 반드시 구현해야 합니다.
클래스의 선언과 정의가 끝나면 해당 클래스를 사용할 수 있습니다. main() 메서드는 객체를 저장하는 id 변수 obj를 선언하고 동시에 Test 클래스의 인스턴스를 생성하고 이것에 할당합니다. [Test alloc]는 Test 클래스의 클래스 메소드 alloc을 호출한다는 뜻입니다. Test 클래스의 alloc은 정의한 적이 없지만, alloc 메소드는 Object 루트 클래스에 정의되어 있기 때문에 정상적으로 작동합니다. alloc 메소드는 Test 클래스의 선언 정보에 따라 적절히 메모리를 할당하고 생성된 객체를 반환해 줍니다.
Test 클래스는 인스턴스 메서드인 method를 정의하고 있기 때문에, 생성된 객체의 method 메서드를 메시지식 방식으로 호출할 수 있습니다. 프로그램은 인스턴스가 생성된 직후에
[obj method] 라는 메시지 식을 작성하여 Test 클래스의 method 메서드를 호출합니다. 그 결과, 화면에는 YoonSeo on your lap 이라는 문자열이 나타나는 것입니다.
덧붙여서, 객체를 생성하고 곧바로 메소드를 호출할 경우 인스턴스 생성 및 method 메소드의 호출은 다음과 같이 한번에 작성할 수도 있습니다.
[[Test alloc] method]
이 경우 내부에서 [Test alloc]이 먼저 실행되고 생성된 인스턴스를 참조하는 id 형식의 객체가 반환됩니다. 그리고 반환된 id 형식의 객체에 대해 한번 더 method 메시지를 전송하여 메서드를 호출하는 것입니다. 이러한 메시지식은 중첩될 수 있습니다. 이것은 C++의(Test.alloc()) method()라는 함수 호출에 비유될 수 있습니다.
위의 프로그램과 같이 클래스 선언 및 정의를 하는 위치는 자유롭지만 관습적으로 클래스 선언은 헤더 파일에 정의하며, 클래스의 선언은 클래스 이름과 동일한 이름의 *. m 파일에 작성합니다.