objective c 상속과 클래스
상속이란?
다른 클래스 정의의 일부분을 확장 또는 변경해서 새로운 클래스를 정의하는 기능을 상속이라고 한다. 새롭게 작성한 클래스에서 상속의 기본이 되는 클래스를 슈퍼클래스라고 부르고, 반대로 상속받은ㅇ 클래스는 서브클래스라고 부른다.
상속의 특징은 무엇인가?
서브클래스는 슈퍼 클래스의 정의를 모두 이어 받는다. 서브 클래스는 슈퍼 클래스에 있던 인스턴스 변수도 이어받고 슈퍼 클래스와 같은 메서드를 실행할 수도 있다.
1. 새로운 메서드 추가하기
2. 새로운 인스턴스 변수 추가하기
3. 슈퍼 클래스 메서드를 다른 정의로 바꾸기
클래스 계층이란?
유용한 클래스를 상속해서 새로운 클래스를 더 만들거나, 상속으로 정의한 클래스를 다시 상속해서 다른 클래스를 만드는 작업을 반복하면 트리 모양의 클래스 계층 구조가 생겨난다.
루트 클래스란?
루트 클래스는 최상위 클래스이다. Cocoa 환경에는 루트 클래스로 NSObject 클래스가 있어서 다른 모든 클래스가 직접 또는 간접적으로 NSObject 클래스를 상속한다. 따라서 스스로 새로운 클래스를 만들때도 NSObject 또는 기존 클래스를 상속한 서브 클래스로 정의해야 한다.
이렇게 클래스 계층을 구성하면 objective c의 모든 객체는 NSObject 클래스에서 정의한 인스턴스 성질을 이어받는다. objective c 객체를 객체로 다룰 수 있는 건 실제로 이 NSObject 클래스에 객체의 기본 동작이 정의되어 있기 때문이다. 즉, 모든 클래스가 루트 클래스를 상속하지 않으면 객체로 다룰 수 없다.
상속관계선언
@interface 클래스명 : 슈퍼 클래스명
{
인스턴스 변수 선언;
}
메서드 선언
@end
인스턴스 변수 선언에는 서브 클래스에서 새롭게 추가하는 인스턴스 변수만 적는다. 추가할 인스턴스 변수가 없을 땐 { } 만 적거나 생략한다.
메서드 선언도 서브 클래스에 추가하는것만 적는다. 다만, 슈퍼 클래스에서 정의한 메서드를 서브 클래스에서 재정의(변경)하고 싶을 땐, 인터페이스에도 서술하고 변경 사항을 주석으로 남겨야 한다.
슈퍼 클래스 메서드를 호출하고 싶을때는?
슈퍼 클래스 메서드를 서브 클래스에서 이용하고 싶다면 super 라는 특별한 이름에 대해 메시지를 보내면 된다. 그러면 자신의 메서드가 아닌 슈퍼 클래스 또는 상속의 상위 클래스에 정의된 메서드가 호출된다. 참고로 super는 self와 달리 특정 객체를 나타내지 않는다. 따라서 변수에 대입하거나 메서드 반환값으로 사용할 수 없다. 슈퍼 클래스 메서드를 호출하는 목적 외에는 사용할 수 없다.
새로 인스턴스 변수를 추가하면서 초기화하거나 슈퍼 클래스와 다른 방법으로 초기화 하고 싶을때는?
이럴 때는 서브 클래스에서 초기자를 재지정한다. 처음에 슈퍼 클래스의 init을 쓴다는것이 중요하다. 이에 따라 슈퍼 클래스에서 정의한 인스턴스 변수 등이 초기화 되므로 그 아래에 서브 클래스의 독자적인 초기화 코드를 작성하면 된다.
모든 클래스 정의를 이 방법으로 초기화한다고 정해두면 반드시 루트 클래스 NSObject의 init을 실행하게 된다. 이 초기화 메서드를 실행하지 않으면 객체로 다룰 수 없다. 동시에 어떤 슈퍼 클래스에서 정의한 인스턴스 변수를 초기화에서 빠트리는 일도 없어진다.
//보조초기자
- (id)init
{
//반드시 슈퍼 클래스 초기자를 먼저 호출
self = [super init];
//슈퍼 클래스에서 인스턴스가 돌아오면
if (self != nil) {
//여기서 부터 서브 클래스용 초기화 코드를 작성
}
return self;
}
슈퍼 클래스가 NSObject라면 항상 인스턴스가 초기화 되므로 반환값에 신경쓰지 않아도된다. 하지만 인수를 사용하거나 파일에서 값을 읽어 초기화하는 초기자라면 값이 잘못되었거나 파일 읽기가 안되어 초기화에 실패하기도 한다. 이런 상황에 대응하기 위해 초기자에서 슈퍼클래스가 돌려준 값을 확인하는 습관을 들여야 한다.
인스턴스 객체를 생성하는 메서드 alloc은 인스턴스 객체가 가진 인스턴스 변수값을 모두 0으로 초기화 한다. (isa 제외) 따라서 새롭게 추가된 인스턴스 변수 초기값이 0으로, 문제가 없다면 명시적인 초기화는 생략해도 된다. 하지만 생략인지 빠트린 것인지 알기 어려우므로 초기값이 0이라고 주석을 다는것이 좋다.
초기자에서 모든 인스턴스 변수를 설정하는 방법과 초기자는 기본 설정만 하고 초기화한 직후 인스턴스 변수값을 다른 메서드에서 설정하는 방법이 있다. 클래스 Volume의 예로, init에서 초기화한 다음 setMax: 같은 메서드를 사용해 최대값, 최소값, 조절폭을 설정할 수 있다. 초기화 하고 나면 변하지 않는 값이나 반드시 초기값을 설정해야 하는 것은 초기자 인수로 지정하는 게 원칙이다.
상속을 사용한 메서드 추가 예제를 만들어 보시오.
응용예제
Volume.h (슈퍼클래스 정의부)
최소값, 최대값, 현재불륨을 나타내는 인스턴스 변수와 소리 높이기 , 낮추기, 소리 초기화하기등 메소드가 있다.
#import <Foundation/NSObject.h>
@interface Volume : NSObject
{
int val;
int min, max, step;
}
- (id)initWithMin:(int)a max:(int)b step:(int)s;
- (int)value;
- (id)up;
- (id)down;
@end
Volume.m (슈퍼클래스 구현부)
#import "Volume.h"
@implementation Volume
//슈퍼클래스의 지정초기자에서만 NSObject의 init 보조초기자를 호출 할 수 있다.
- (id)initWithMin:(int)a max:(int)b step:(int)s
{
self = [super init];
if (self != nil) {
val = min = a;
max = b;
step = s;
}
return self;
}
- (int)value
{
return val;
}
- (id)up
{
if ((val += step) > max)
val = max;
return self;
}
- (id)down
{
if ((val -= step) < min)
val = min;
return self;
}
@end
MuteVolume.h (서브클래스 정의부분)
음량을 바로 최소값으로 설정하는 음소거 기능이 있는 새로운 클래스 생성. 단순히 mute 메시지가 호출되면 음량값이 최소가 되는 기능.
슈퍼클래스 Volume 클래스를 상속받아서 최소값, 최대값, 현재불륨을 나타내는 인스턴스 변수와 소리 높이기 , 낮추기, 소리 초기화하기등 메소드를 사용할 수 있다.
#import "Volume.h"
@interface MuteVolume : Volume
- (id)mute;
@end
MuteVolume.m (서브클래스 구현부분)
#import "MuteVolume.h"
@implementation MuteVolume
- (id)mute
{
val = min;
return self;
}
@end
실행부분
서브클래스에서 슈퍼 클래스의 초기화 메소드를 호출했다. 서브클래스에 initWithMin 메소드가 없기때문에 슈퍼클래스의 초기화 메소드 initWithMin 메소드가 호출되고 슈퍼 클래스의 인스턴스 변수들이 초기화 된다.
#import "MuteVolume.h"
#import <stdio.h>
int main(void)
{
id v;
char buf[8];
/* 슈퍼클래스 Voulme을 상속해서 슈퍼클래스의 초기화 메소드를 사용가능*/
v = [[MuteVolume alloc] initWithMin:0 max:10 step:2];
while (scanf("%s", buf) > 0) {
switch (buf[0]) {
case 'u': [v up]; break; //슈퍼클래스의 메소드 사용가능
case 'd': [v down]; break;
case 'm': [v mute]; break;
case 'q': return 0;
}
printf("Volume=%d\n", [v value]);
}
return 0;
}
실행
상속을 사용해 클래스를 정의했다면 슈퍼 클래스의 클래스 정의도 모두 함께 컴파일하고 링크해야 한다. 그렇지 않으면 슈퍼 클래스 메서드를 사용하지 못한다.
clang main.m Volume.m MuteVolume.m -framework Foundation
./a.out
상속을 사용한 메서드 재정의 예제를 만들어 보시오.
응용예제
Volume 슈퍼클래스는 위와 같다. 이번에는 mute 메시지를 한번더 보내면 원래음량으로 되돌리고, 음소거된 상태에서는 up이나 down메시지를 받으면 최소값을 돌려주지만 내부적으로 관리하는 음량값은 변경하도록 수정해본다.
#import "Volume.h"
@interface MuteVolume : Volume
{
BOOL muting;
}
//오버라이드 (재정의)
- (id)initWithMin:(int)a max:(int)b step:(int)s;
- (int)value;
- (id)mute;
@end
서브 클래스에서 슈퍼클래스의 초기화 메소드를 재정의 했기때문에 슈퍼클래스의 초기화 메소드를 호출해주고 난 뒤에 서브클래스의 인스턴스 변수를 초기화 해주어야 한다.
/*** Copyright: OGIHARA Takeshi, Feb. 2008 ***/
#import "MuteVolume.h"
@implementation MuteVolume
/* override */
/*
우선 슈퍼클래스에 초기화를 요청한 후 새롭게 추가한 인스턴스 변수 muting을 초기화 한다.
서브 클래스에서 초기화 설정은 반드시 슈퍼 클래스의 초기화 설정 후에 한다.!
*/
//서브클래스의 지정초기자만이 슈퍼클래스의 지정초기자를 호출할 수 있다.
- (id)initWithMin:(int)a max:(int)b step:(int)s
{
self = [super initWithMin:a max:b step:s];
if (self != nil)
muting = NO;
return self;
}
/* override */
/*
음소거 상태인지의 여부에 따라 반환값을 변경한다.
음소거 상태면 항상 최소값반환한다.
*/
- (int)value
{
return muting ? min : val;
}
/*
mute는 음소거 상태값을 반전 시킨다.
*/
- (id)mute
{
muting = !muting;
return self;
}
@end
결과
상속과 메소드 호출 예
클래스 B 인스턴스와 클래스 C 인스턴스는 호출되는 메서드가 다르다.
#import <Foundation/NSObject.h>
#import <stdio.h>
@interface A: NSObject
- (void)method1;
- (void)method2;
@end
@implementation A
- (void)method1 { printf("method1 of Class A\n"); }
- (void)method2 { printf("method2 of Class A\n"); }
@end
@interface B: A
//클래스 A의 메소드 재정의
- (void)method2;
@end
@implementation B
- (void)method2 {
printf("method2 of Class B\n");
printf("self --> ");
[self method1]; //self는 메시지 처리를 하는 인스턴스 자신을 나타낸다.
printf("super--> ");
[super method2];
}
@end
@interface C: B
//클래스 A의 메소드 재정의
- (void)method1;
@end
@implementation C
- (void)method1 { printf("method1 of Class C\n"); }
@end
int main(void)
{
id classB = [[B alloc] init];
id classC = [[C alloc] init];
printf("--- B 클래스의 인스턴스 ---\n");
[classB method1];
[classB method2];
printf("--- C 클래스의 인스턴스 ---\n");
[classC method1];
[classC method2];
return 0;
}
결과
--- B 클래스의 인스턴스 ---
method1 of Class A
method2 of Class B
self --> method1 of Class A
super--> method2 of Class A
--- C 클래스의 인스턴스 ---
method1 of Class C
method2 of Class B
self --> method1 of Class C
super--> method2 of Class A
Program ended with exit code: 0
지정초기자란?
각 클래스는 새로운 인스턴스를 생성한 후 바로 초기화 작업을 한다. 이때 사용하는 것이 - (id)init 메소드 이다. 모든 클래스는 고유의 멤버영역이 있으며, 이를 초기화하기 위해서 고유의 초기화 메소드가 오버라이드 되어야 한다.
- 지정초기화 : 각 클래스에서 초기화를 위해 반드시 실행되어야 하는 메소드. 보통 가장 많은 인수를 가진다.
- 보조초기화 : 지정초기화 호출을 통해 간접적으로 초기화를 수행하는 메소드
1. 각 클래스의 지정초기화만이 상위 클래스의 지정초기화를 호출한다.
2. 각 클래스의 보조초기화(init)는 자기 클래스의 지정초기화를 호출한다.
예제
#import <Foundation/Foundation.h>
@interface Volume : NSObject
{
int val;
int min, max, step;
}
-(id)initWithMin:(int)a max:(int)b step:(int)s;
-(id)init;
@end
@implementation Volume
//슈퍼클래스의 지정초기자
-(id)initWithMin:(int)a max:(int)b step:(int)s
{
NSLog(@"슈퍼클래스의 지정초기자");
self = [super init];
if(self != nil)
{
val = min = a;
max = b;
step = s;
}
return self;
}
//부모 클래스의 보조 초기자
-(id)init
{
NSLog(@"부모 클래스의 보조 초기자");
self = [self initWithMin:0 max:10 step:1];
return self;
}
@end
@interface ChildVolume : Volume
-(id)initWithMin:(int)a max:(int)b step:(int)s;
@end
@implementation ChildVolume
//서브 클래스의 지정초기자
-(id)initWithMin:(int)a max:(int)b step:(int)s
{
NSLog(@"서브 클래스의 지정초기자");
//self = [super init]; //서브 클래스의 지정초기화에서 슈퍼 클래스의 보조 초기화 호출 할 수 없다.! 에러난다.
//지정초기자만이 상위 클래스의 지정초기자를 호출 할 수 있다.
self = [super initWithMin:0 max:100 step:10];
return self;
}
@end
int main(int argc, char *argv[])
{
ChildVolume *p = [[ChildVolume alloc] initWithMin:0 max:5 step:1];
[p release];
return 0;
}
결과
'아이폰 개발 > Objective C' 카테고리의 다른 글
objective c 소유권 (0) | 2020.12.27 |
---|---|
objective c 동적결합 (0) | 2020.12.26 |
objective c 프로그램 (0) | 2020.12.25 |
objective c 객체 개념 (0) | 2020.12.25 |
2장 클래스 (0) | 2020.08.23 |