3. 클래스의 선언과 정의
컴파일러 디렉티브
Objective-C 언어는 오브젝트 지향형 언어이기 때문에, 재이용가능한 데이터와 제어를 패키지화한 템플릿을 제공하는 일을 할 수 있습니다. 이것을 일반적으로 클래스라고 부르고, 클래스는 메모리실체를 생성하기 위한 정보입니다.
클래스의 개념은 C언어의 구조체를 발전시킨 것으로, 구조체와 같은 데이터의 연관부에 추가하여, 데이터와 제어(함수)를 연관시킵니다. 그렇기 때문에 코드는 항상 데이터를 처리하는 전용의 함수를 안전히 불러내는 일을 하고 일련의 기능을 한 개의 서브시스템으로 제공하는 일이 가능한 것입니다.
클래스를 이용하는 것에는 구제체와 같이 일단 선언이 필요합니다. Objective-C 언어의 클래스의 선언은 예약어나 구문이 아닌 컴파일러 디렉티브를 이용합니다. 컴파일러 디렉티브는 컴파일러에 Objective-C로 확장되어진 클래스의 선언이나 설치등에 이용되어 @마크로 시작된다는 특징을 가집니다.
클래스를 선언하는 컴파일러 디렉티브는 @interface 로 시작하고 @end로 끝납니다. 이들 사이에 클래스에 관련되는 변수영역과 함수를 선언합니다. Objective-C의 클래스 선언과 정의의 관계는 다른 언어에 비해 복잡하므로, 처음에는 당황할지도 모르겠습니다.
{
인스탄스 변수선언
...
}
메소드선언
@end
이 시점에서 C언어계의 오브젝트지향언어와는 선언방법이 상당히 다른 것이 보입니다. 인스턴스변수와는 클래스에 관련된 변수를 말하는 것으로, 구조체의 멤버와 같은 존재입니다. 단, 구조체와는 달리 인스턴스변수에는 클래스의 외부에서 억세스하는 일은 불가능합니다. 인스턴스변수를 이용할 수 있는 것은 원칙적으로 클래스에 관련된 메소드만 가능합니다. 인스턴스변수가 존재하지 않는 경우는 인스턴스변수 선언과 {}를 생략하는 일이 가능합니다.
메소드라는 것은 클래스에 관련된 함수를 말합니다. 통상의 함수와 차이는, 메소드는 메소드를 불러내는 클래스의 실체에 연관되어져 있기 때문에 직접 인스턴스변수에 억세스 하는 일이 가능합니다.
클래스명의 직후에 부모클래스를 지정하는 일이 있습니다만, 부모클래스라는 것은 무엇일까요? 오브젝트지향은 한번 정의되어진 기능을 확장하는 계승이라고 불리는 기능을 제공하지 않으면 안됩니다. 계승관계는 생물의 진화론에 예를 들면, 개나 고양이등의 여러 가지 실체에 공통되는 추상적인 기능은 표유류클래스로 정의하고 개클래스나 고양이클래스는 포유류클래스를 계승한다는 것처럼 프로그램하는 것입니다.
부모클래스를 지정하지 않는 클래스는, 다른 클래스의 정점이 되기 때문에 루트클래스라고 불립니다. Java나 .NET에서는 , 플랫폼이 기본이 되는 루트클래스를 제공하는 구조를 채용하고, C++언어는 루트클래스를 정하지 않고 루트클래스를 개발자가 개발할 수 있게 설계합니다. Objective-C 언어는 그 중간적인 형태로, 원칙적으로는 루트클래스에서 개발하는 일을 할 수 있지만, 사실상은 컴파일러등의 처리계가 루트클래스를 제공하지 않으면 안됩니다.
루크클래스는 처리계에 따라 다릅니다만 GCC라면 Object 클래스가, Mac OS X의 Cocoa 환경이라면 NSObject 클래스가 루트클래스가 됩니다. 독자의 루트클래스를 개발할 경우라면 부모클래스를 지정하지 않습니다만, 통상적으로는 처리계가 제공하는 루트클래스를 계승합니다. 루트클래스를 개발하는 경우에 선언은 다음과 같이 할 것입니다.
{
인스턴스변수선언
...
}
메소드선언
@end
어째서, 루트클래스를 계승하지 않으면 안되는가 하는 것은 루트클래스는 클래스가 정상적으로 동작하기 위하여 필요한 기본적인 기능을 제공한다고 하는 역할이 있기 때문입니다. 루트클래스가 존재하지 않는다면, 클래스의 실체를 작성하기 위하여 번잡한 수속을 모든 클래스에 기술하지 않으면 안 됩니다. 힙메모리의 확보나 해방등의 기본적인 처리수속은 루트클래스에 맡겨두고, 개발자는 개발목적의 기능을 기술하는 일에 집중할 수 있는 것입니다.
인스턴스 변수의 선언은 통상의 변수와 같습니다만, 메소드의 선언은 함수의 선언과는 상당히 다릅니다. 이 메소드 선언의 사양은 C++에 비하여 Objective-C 나 C언어프로그래머에게 받아들여지지 않는 커다란 요인의 한 개라고 생각될 것입니다. 메소드의 선언은 다음과 같습니다.
어째서, 함수의 선어과 이정도로 다른 것인가, C언어와 하이브리드언어와의 사양상의 문제가 뚜렷이 드러나게 됩니다. 최초의 마이너스 기호 - 는 이 메소드가 클래스의 실체인 오브젝트에 관련된 것을 보여줍니다. 통상의 메소드는 - 기호로 좋습니다만, 오브젝트가 아닌 클래스에 관련된 메소드의 선언이라면 + 기호를 지정합니다. - 기호의 메소드를 인스턴스메소드라고 부르고, + 기호로 시작하는 메소드를 클래스메소드라고 부릅니다. 인스턴스메소드와 클래스메소드의 차이점은 뒤에 자세히 설명하겠지만, 일반적으로 메소드는 - 로 시작한다고 기억해 주세요.
반환형을 보면 알겠지만, 메소드선언에는 형을 () 로 둘러쌉니다. 메소드가 인수를 받아들일 경우는 콜론 : 에 이어 가인수 리스트를 지정합니다. 가인수리스트도 통상의 변수선언과는 달리 변수형을 () 의 속에 지정하고, 그것에 이어 변수명을 지정합니다.
C언어의 함수의 기본반환형은 int 였습니다만, 메소드의 반환형의 기본값은 id 형이라고 하는 Objective-C 에 추가된 오브젝트를 나타내는 범용형이 되어있습니다. 반환형은 생략할 수 있습니다만, 통상적으로는 생략하지 않고 명시적으로 선언합시다. 반환값을 반환하지 않을 경우에는 (void)를 지정합니다.
- (void)method;
@end
이 Test 클래스의 선언은 반환값을 반환하지 않고, 인수를 받아들이지 않는 method 메소드를 선언하는 단순한 클래스를 선언합니다. 클래스명이나 메소드명의 명명규칙은, C언어의 변수나 구조체명의 명명규칙과 같이, 임의로 식별할수 잇는 알파벳부터 시작하는 식별자가 아니면 안됩니다. 단 관습적으로 클래스명은 대문자로 시작하고, 메소드명은 소문자로 시작합니다.
자, 클래스와 메소드를 무사히 선언할수 있었습니다만, 메소드는 메소드의 이름과 형만을 선언한 것만으로, 처리코드를 기술하는 정의가 존재하지 않습니다. 실은 Objective-C 에는 클래스의 선언과 정의가 완전히 분리되어져 있어, 클래스의 선언에는 인스턴스 변수와 메소드의 선언밖에 할수 없습니다. 클래스를 선언부터 정의와 구체화하는 것에는 @implementation 컴파일러디렉티브를 사용하여 클래스를 정의하지 않으면 안됩니다.
메소드정의
...
@end
@implementation 컴파일러 디렉티브에는 @interface 로 선언된 클래스의 실체를 기술합니다. 선언한 메소드는 여기에 정의되어지지 않으면 안됩니다. 클래스에 메소드가 선언되어져 있지 않은 경우에, @implementation 에 기술할 것은 아무것도 없겠지만, 그래도 정의되지 않은 클래스는 @implementation 에 명시적으로 실체되지 않으면 안됩니다.
클래스의 인스턴스화
클래스를 선언, 또는 정의한다면 클래스를 이용하는 일이 가능하게 됩니다만, 이대로는 사용할 수 없습니다. 클래스를 이용하기 위하여 필요한 메모리 영역을 할당하고, 적절한 초기화를 행하여 처음으로 클래스가 정상적으로 동작하게 됩니다. 클래스의 선언정보에 기초하여, 메모리를 할당하는 일을 인스턴스화라고 부르고 확보된 메모리영역을 인스턴스라고 부릅니다. 인스턴스라고 부르는 경우, 통상적으로는 클래스의 정보에 기초하여 확보된 실체를 나타내지만, 일반적으로는 오브젝트라고 부릅니다.
C++언어나 Java 언어등, 일반적인 오브젝트지향형언어에는 new연산자가 존재하고, 이 연산자가 클래스의 선언정보에 기초하여 인스탄스를 생성하고 초기화해줍니다. 그리고, 놀라운 것은 Objective-C언어는 인스탄스의 생성도 포함하여 클래스가 하지 않으면 안되게 정해져 있습니다. 그러나 클래스의 인스턴스화에는 오브젝트에 필요한 사이즈를 조사하고, 메모리를 할당하는 등, 번잡한 초기화처리가 필요합니다. 이것을 모든 클래스에 넣는 것은 현실적이 아니므로, 이작업을 일단 받아주는 것이 루트클래스의 존재인 것입니다.
Object 클래스는 Object 클래스를 포함한 그것을 계승하는 클래스의 인스턴스를 적절히 생성하는 alloc 클래스메소드를 정의합니다. 통상의 인스턴스메소드는 메소드를 불러내기 위하여 인스턴스가 필요하지만, 클래스메소드는 인스턴스가 아니라도 실행할 수 있다고 하는 성질이 있습니다. 그래서, alloc 메소드는 인스턴스가 존재하지 않아도 불러내도 문제는 없습니다. alloc 메소드는 인스턴스를 생성하기 위한 클래스메소드이기 때문에, 종종 팩토리 메소드라고도 불립니다.
이것이, Object 클래스에 선언되어있는 alloc메소드입니다. +로 시작하고 있기 때문에, 이 메소드는 클래스메소드인 것을 어필하고 있습니다. 반환형이 생략되어있기 때문에 디폴트의 id형이 반환됩니다.
id형은 오브젝트를 나타내는 범용형으로, 일종의 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("Kitty on your lap\n");
}
@end
int main() {
id obj = [Test alloc];
[obj method];
return 0;
}
자, 이것이 간단한 Objective-C 에 의한 클래스의 실현입니다. @interface 에 의한 클래스의 선언에는, 루트클래스 Object를 계승하는 Test 클래스를 선언하고 있습니다. Test클래스에는 반환값을 리턴하지 않고, 인수를 받아들이지 않는 method 메소드를 선언하고 있습니다. 그렇게 때문에 @implementartion 에 이 메소드를 구현하지 않으면 안 됩니다.
클래스의 선언과 정의가 끝나면, 그 후 그 클래스를 이용하는 일을 할 수 있게 됩니다. main() 메소드에는 오브젝트를 격납하는 id형의 변수 obj를 선언하고, 동시에 Test 클래스의 인스탄스를 생성하여 이것을 대입합니다. [Test alloc] 에는 Test 클래스의 클래스메소드 alloc를 호출한다 라는 의미입니다. Test 클래스에 alloc 는 정의 되어 있지 않습니다만 alloc 메소드는 Object 루트클래스에 정의되어져 있기 때문에 정상적으로 동작합니다. alloc 메소드에는 Test 클래스의 선언정보에 기초하여 적절히 메모리를 할당하여 생성된 오브젝트를 반환해 줍니다.
Test 클래스는 인스탄스메소드 method를 정의하고 있기 때문에 생성한 오브젝트의 method 메소드를 메시지식에서 기동할수 있습니다. 프로그램에는 인스탄스생성 직후에 [obj method]라고 하는 메시지식을 기술하여 Test 클래스의 method 메소드를 기동하고 있습니다. 그 결과, 화면에는 Kitty on your lap 와 문자열이 표시될 것입니다.
덧붙여, 오브젝트를 사용하고 버리게 되는 경우, 인스탄스의 생성과 method 메소드의 호출은 다음과 같이 기술하는 것도 가능합니다.
이 경우, 내측의 [Test alloc]가 먼저 실행되어지고, 생성된 인스탄스를 참조하는 id형의 오브젝트가 반환됩니다. 그리고, 반환된 id형의 오브젝트에 대하여, 마침내 method 메시지를 송신하는 것으로 메소드를 호출하는 것입니다. 이처럼, 메시지식은 중첩되는 것이 가능합니다. 이것은 (Test . alloc()) . method() 와 같이 함수의 호출관계에 비교될 수 있을 것입니다.
상기의 프로그램처럼, 클래스의 선언과 정의의 장소는 자유이지만, 관습적으로 클래스의 선언은 헤더파일에, 정의는 클라스명과 같은 이름의 *.m 파일에 기술합니다.
댓글 없음:
댓글 쓰기