[Objective-C] 객체 형식과 동적 결합
출처 : OS X 구조를 이해하면서 배우는 Objective-C Chap 4.
Notice : 정리자(돼지왕 왕돼지)가 remind 하고 싶은 내용이나 모르는 내용 기반으로 정리하는 것이기 때문에 구체적인 내용은 책을 사서 보시기를 권장드립니다.
4.1. 동적 결합
* 4.1.1. 동적 결합이란
-
Objective-C 에서 어떤 객체가 그 메시지를 처리할 수 있는지 아니면 없는지, 어떻게 처리하는지는 실제로 메시지를 보낼 대 정해진다.
송신된 메시지에 대응해서 어떤 메시지가 실행될지가 실행 시에 결정되는 방식을 동적 결합 (dynamic binding) 또는 동적 구속이라고 부릅니다.
-
C 언어는 컴파일할 때 대부분이 정적으로 결정되므로 동적 결합 기능이 없다고 봐도 됩니다.
함수 포인터 같은 기능을 사용해 C 언어에서도 동적 결합과 비슷한 효과를 얻을 수는 있지만 쉽게 만들 수 있는 것은 아니므로 비현실적이다.
* 4.1.2. 다형성
-
메시지를 보내더라도 리시버 객체에 따라 적절한 메서드가 선택되어 실행되는 성질을 다형성(polymorphism)이라고 부른다.
4.2. 형식과 클래스
* 4.2.1. 클래스명을 형식으로 사용
* 4.2.2. 빈 포인터 nil
-
리시버가 nil 이라면 그 메시지의 반환값은 어떻게 될까?
반환값 형식이 객체일 때는 nil, 포인터라면 NULL, 정수라면 0으로 정해져 있다.
하지만 그 외의 형식인 구조체나 실수가 반환값이라면 Mac OS X 버전이나 구조체 크기에 따라 조금씩 다르다.
따라서 객체, 포인터, 정수가 아니라면 정해지지 않은 값이 돌아온다고 생각하는 편이 났다.
-
char 형을 가리키는 포인터 p 가 NULL 이 아니고 또한 빈 문자열도 아니다라는 걸 조사하는 방법은..
if( p && *p )
* 4.2.3. 형식의 정적 확인
-
id 형은 C 언어로 치면 (void *)형 같은 것으로, 어떤 객체도 나타낼 수 있어서 다형성의 장점을 살린 상당히 유연한 프로그래밍이 가능해진다.
하지만 예상하지 않은 클래스를 잘못해서 사용하는 프로그램을 만들 위험도 있다.
-
Objective-C 에서 사용하는 클래스가 정해져 있으면, 클래스명을 명시해서 형식 선언을 하면 컴파일할 때 메시지가 처리 가능한지를 확인하는 것이 좋다.
이렇게 하면 의도하지 않은 클래스를 잘못 사용하는 위험성이 낮아진다.
-
다음과 같이 변수 a 가 클래스 A 인스턴스일 때 & B가 A 를 상속했을 때, a 가 B 인스턴스가 아님에도 다음처럼 cast 해도 상관이 없다.
[(B *)a whoAreYou];
실행되는 메서드가 캐스트에 따라 정적으로 정해지는 것이 아니기 때문이다.
* 4.2.4. 정적 형식 확인 정리
-
1. id 형으로 선언한 변수라면 어떤 메서드 호출이라도(실제로 동작하는지는 별개) 작성이 가능하다.
2. id 형으로 선언한 변수와 클래스명을 형식으로 선언한 변수 사이에는 상호 대입이 가능하다.
3. 클래스명을 사용해서 변수를 선언하면 그 클래스가 대응하지 못하는 메서드 호출에는 경고가 나온다.
4. 클래스명을 형식으로 사용한 변수에 그 클래스의 서브 클래스를 형식으로 사용하는 변수를 대입해도 된다.
5. 클래스명을 형식으로 한 변수에 그 클래스의 슈퍼 클래스를 형식으로 하는 변수를 대입하면 안 된다.
6. 프로그램 코드 속에 있는 선언과 상관없이 실행할 경우 실제로 변수에 들어 있는 객체 메서드가 사용된다.
7. id 형은 (NSObject *) 형이 아니다.
4.3. 프로그래밍과 형식 선언
* 4.3.1. 서명이 다를 때
-
메시지 셀럭터에는 인수와 반환값 형식 정보가 없지만, 셀럭터와 형식 정보를 합쳐서 서명(signature)라고 부른다.
-
Cocoa 에는 메서드의 인수 개수와 형식, 반환값 형식 같은 정보를 객체로 관리하는 클래스 NSMethodSignature 가 있어서 이 인스턴스를 서명(메서드 서명)이라고 부르기도 한다.
-
리시버나 인수가 되는 객체 형식이 실행 시에 정해진다면 메시지 서명에 어떤 제약이 있어야 제대로 컴파일된다.
결국 Objective-C 에서는 기본적으로 셀렉터가 같은 메시지는 인수와 반환값 형식(서명)을 통일하는 것이 좋다.
( 그러나 반환값을 변형할 수는 있다. )
-
Foundation 프레임워크에도 Application 프레임워크에도 셀렉터는 같지만 서명은 다른 메서드가 몇몇 있다.
따라서 완벽하진 않아도 된다.
-
컴파일할 때 서명이 불일치하는 부분이 발견되면 경고가 나오고 문제가 된 부분을 알려준다.
메서드명을 바꾸거나 정적인 형식 확인이 되도록 수정해야 한다.
cf) 중첩
대다수 프로그래밍 언어는 1+2 라고 적을 수도 있고, 1.0 + 2.2 라고 적을 수도 있다.
양쪽 계산 결과의 형식이 다르다는 점에서 + 연산자는 정수 덧셈과 실수 덧셈 기능을 연산 대상에 따라 골라서 사용한다.
연산자, 함수나 메서드에 여러 기능을 정의해서 사용되는 문맥에 따라 적절한 기능을 선택하도록 하는 걸 다중 정의(definition overloading) 또는 중첩(overloading)이라고 부른다.
Objective-C 는 실행 시 인수 형식이 결정되므로 overloading 이 없다.
하지만 동적 결합을 통해 같은 메시지 셀렉터에 여러 기능을 할당하는 것은 overloading 이라고 볼 수 있다.
* 4.3.2. 클래스 전방 선언
-
@class 컴파일러 지시자로 header 에서 사용할 class 를 선언할 수 있다.
이걸 프로그래밍 언어에서 클래스명의 전방 선언(forward declaration)이라고 부른다.
-
@class 컴파일러 지시자 뒤에는 콤마(,)로 구분한 클래스명을 여러 개 지정해도 된다.
문장 끝에는 세미콜론(;)을 둔다.
이 지시자는 여러 번 사용해도 된다.
-
@class 를 사용하면 프로그램의 전체 컴파일 속도를 향상시킬 수 있다.
물론 이런 클래스 메서드를 이용하려면 그 클래스의 인터페이스 정보가 필요하므로 클래스 구현 부분은 헤더 파일을 임포트해야 한다.
-
여러 인터페이스가 서로 클래스명을 사용할 때면 임포트만으로 해결되지 않지만,
@class 를 사용하면 간단히 해결된다.
* 4.3.3. 캐스트를 사용한 예제
4.4. 인스턴스 변수의 정보 은폐
* 4.4.1. 인스턴스 변수에 접근
-
인스턴스 객체 obj 가 가진 인스턴스 변수 myvar 에 접근하려면 다음처럼 입력하면 된다.
obj->myvar
이 표기법은 포인터로 구조체 멤버에 접근할 때와 같다.
이 방법으로 접근이 가능한 건 인수가 같은 클래스 인스턴스 일 때만 가능하다.
다른 클래스 인스턴스는(슈퍼 클래스 인스턴스라도) 이 방법으로 참조하지 못한다.
또한 클래스명으로 형식을 선언하지 않았을 때도 접근이 불가능하다.
* 4.4.2. 접근자
-
인스턴스 변수에 접근하도록 허용하면 그 뒤로는 클래스 구현 방법이 바뀌어서 혹시라도 인스턴스 변수가 없어지거나 역할이 바뀌거나 하면 그 클래스 인스턴스를 사용하는 다른 모듈 동작이 어떻게 변할지 장담할 수 없다.
이에 비해 속성 접근을 메서드 호출이라는 형태로 작성하면 클래스 구현 방법이 바뀌어도 속성에 접근하는 메서드만 변경하면 대응할 수 있다.
-
서브 클래스 메서드라면 슈퍼 클래스 인스턴스 변수를 참조할 수 있다.
하지만 슈퍼 클래스와 서브 클래스 사이의 독립성을 높이려면 슈퍼 클래스 속성 접근도 메서드 호출을 사용하는 게 좋다.
* 4.4.3. 인스턴스 변수의 가시성
-
인스턴스 변수를 외부에 어떻게 노출할 것인가(보일 것인가, 보인다면 그 범위는 어디까지 할 것인가)를 가시성(visibility)라고 한다.
Objective-C 는 인스턴스 변수에 네 종류의 가시성 지정이 가능하다.
@private
선언한 클래스 내부에서만 접근할 수 있고 서브 클래스는 접근하지 못한다.
같은 클래스 인스턴스라면 메서드 속성에서 -> 연산자를 써서 접근할 수 있다.
@protected
선언한 클래스 및 서브 클래스에서 접근할 수 있다.
같은 클래스 인스턴스라면 메서드 속에서 -> 연산자를 써서 접근할 수 있다.
보통, 아무것도 지정하지 않은 인스턴스 변수는 이 상태가 된다.
@public
어디서나 구조체 멤버처럼 참조할 수 있다.
@package
이 클래스가 정의된 프레임워크 내부에 있다면 @public 처럼 자유롭게 접근할 수 있다.
-
가시성 지정은 클래스 인터페이스의 인스턴스 변수 선언에서 이루어진다.
인스턴스 변수 선언 첫머리 또는 선언과 선언 사이(세미콜론 뒤)에 세 종류의 컴파일러 지시자 중 하나를 적는다.
컴파일러 지시자의 가시성 지정은 그 위치에서부터 인스턴스 변수 선언이 끝날 때까지 또는 다른 컴파일러 지시자가 나올 때까지 그 사이에 있는 인스턴스 변수가 대상이 된다.
컴파일러 지시자가 나오는 순서는 정해진 규칙 같은 것이 없으며 몇 번이라도 나와도 된다.
* 4.4.4. 구현 부분의 인스턴스 변수 정의
-
Xcode 4.2. 이후 clang 컴파일러는 클래스 정의에서 인스턴스 변수의 정의를 구현 부분에 작성할 수 있다.
@implmenetation RGB{
unsinged char red, green, blue, alpha;
}
@end
이런 방법으로 클래스를 정의하면 인터페이스에서 작성한 헤더 파일을 봐도 인스턴스 변수 정보 자체가 없다.
어떤 인스턴스 변수를 정의해서 클래스를 구현하는지에 대한 정보를 클래스 외부에 알릴 필요가 없다면 이 형식을 이용할 수 있다.
클래스 구현 방법이 변경되어 인스턴스 변수 정의가 바뀌더라도 헤더 파일을 변경하지 않아도 된다.
하지만 슈퍼 클래스가 이 방법으로 정의되어 있으면 서브 클래스에서 인스턴스 변수가 보이지 않는다는 것에 주의해야 한다.
-
클래스 외부에 공개되지 않은 인스턴스 변수는 @private 를 써서 지정하는 것보다 예제에서 사용한 방법이 알기도 쉽고 변경도 쉽다.
외부(또는 서브 클래스)에 공개한 인스턴스 변수와도 자연스럽게 구별된다.
-
구현 부분에 정의된 인스턴스 변수는 기본적으로 @private 가시성을 가진다.
하지만 구현 부분의 인스턴스 변수 정의에서도 컴파일러 지시자를 사용해서 가시성을 제어할 수 있다.
4.5. 클래스 객체
* 4.5.1. 클래스 객체란
-
프로그램 실행 시에는 실체가 없어 존재하지 않는다는 의견과 클래스 자체도 하나의 객체라는 의견이 있다.
후자의 입장에서 클래스로 동작하는 객체를 클래스 객체( class object )라고 부른다.
이 때 클래스 정의는 어떤 인스턴스를 작성하는 방법을 서술한 부분과 클래스 자체가 어떻게 동작하는가를 서술한 부분으로 나누어진다.
Objective-C 와 스몰톡은 클래스도 객체라고 보지만 C++ 같은 언어는 클래스는 객체가 아니라고 본다.
-
클래스 객체에는 객체 기능을 위한 자신만의 독자적인 변수와 메서드가 있다.
이것을 클래스 변수와 클래스 메서드라고 한다.
Objective-C 에는 클래스 메서드는 있어도 클래스 변수는 없다.
-
클래스 객체 자체는 언제 생성되는 걸까?
클래스 객체는 프로그램을 실행하면 처음부터 자동으로 클래스마다 하나씩 존재한다.
클래스 객체를 별도로 생성할 필요가 없다.
* 4.5.2. 클래스 객체 형식
-
클래스 객체도 마찬가지로 id 형으로 다룰 수 있으며 클래스 객체용으로 특별히 Class 형이 있다.
Class 형도 id 형과 같이 실제로는 포인터지만 실체가 무엇을 가리키고 있는지 의식할 필요는 없다.
또한 빈 포인터를 나타내기 위해 Nil 같이 있다.
Nil 은 Class 형의 빈 포인터로, 실제값은 0 이다.
-
NSObject 클래스에는 isMemberOfClass: 인스턴스 메서드가 있다.
이 메서드는 클래스 객체를 인수로 받아 리시버가 그 클래스 인스턴스인가를 BOOL 값으로 돌려준다.
* 4.5.3. 클래스 메서드 정의
-
클래스를 상속할 때 슈퍼 클래스가 가진 클래스 메서드 정의는 서브 클래스에서 사용할 수 있다.
-
클래스 메서드 안에서 인스턴스 변수를 참조하지 못한다.
클래스 객체는 하나밖에 없지만 인스턴스는 여러 개 존재할 수 있다.
따라서 클래스 메서드 속에서 인스턴스 변수를 참조하려고 해도 그것이 어떤 인스턴스에 있는지 알 수 없다.
마찬가지로 인스턴스 메서드를 호출하는 것도 안 된다.
-
클래스 메서드를 실행하는 중에는 변수 self 가 클래스 객체 자신을 참조한다.
따라서 어떤 클래스 메서드 안에서 다른 클래스 메서드를 호출할 때는 self 에 대해 클래스 메서드를 호출하는 메시지를 보내면 된다.
하지만 인스턴스 메서드와 마찬가지로 self 가 나타내는 클래스가 무엇인지 알고 주의해야 한다.
-
슈퍼 클래스의 클래스 메서드를 호출할 때는 super 를 사용한다.
* 4.5.4. 클래스 변수
-
Objective-C 에는 클래스 변수를 정의하는 구문 구조가 없다.
Objective-C 구현 파일 속에 static 지정자와 함께 정의된 변수를 그 클래스의 클래스 변수 대신 사용한다.
-
C 언어는 함수 및 함수 외부에 정의된 변수(외부 변수)에 아무런 지정도 하지 않으면 자동으로 전역 변수가 되어 프로그램 어디에서나 참조할 수 있다.
하지만 static 지정자를 붙여서 정의하면 해당 변수와 함수는 정의 파일 내부에서만 유효하다.
-
Objective-C 에서도 구현 파일 안에서 static 지정자를 사용해 변수를 선언하면 그 변수는 구현 파일 내부에서만 참조할 수 있다.
즉, 해당 클래스의 클래스 메서드와 인스턴스 메서드만 참조할 수 있다.
하지만 조금 문제가 있는 방법이다.
상속을 사용해 클래스를 정의할 때 슈퍼 클래스에서 클래스 변수 대신 사용하던 변수를 서브 클래스에서는 참조할 수 없다.
서브 클래스에서도 참조할 수 있는 클래스 변수를 만들려면 슈퍼 클래스에서 static 지정자를 사용하지 말아야 한다.
-
클래스 객체 속성에 접근하려면 접근자 메서드를 클래스 메서드로 정의해야 한다.
서브 클래스에도 해당 메서드가 상속되므로 자동으로 슈퍼 클래스 내부의 지역 변수에 접근할 수 있다.
-
Objective-C 에서 클래스 변수는 그 클래스 내부 구현에서만 사용하는 것으로, 원칙적으로 외부에는 보여주지 않는다고 생각하며 클래스를 설계해야 한다.
* 4.5.5. 클래스 객체 초기화
-
클래스 객체는 프로그램이 실행되는 시점에 이미 존재하기 때문에 생성 직후 메시지를 보내는 것은 불가능하다.
Objective-C 에서는 루트 클래스 NSObject 에 initialize 메서드가 있어서 이것으로 각 클래스를 초기화한다.
-
프로그램이 실행되고 나서 어떤 클래스가 처음으로 메시지를 받으면 바로 initialize 메서드가 자동으로 실행되는데 그보다 먼저 슈퍼 클래스의 클래스 객체에도 initialize 메서드가 호출된다.
각 클래스의 initialize 메서드는 단 한 번만 호출된다.
즉, initialize 메서드에 작성된 코드는 클래스 객체( 및 인스턴스 객체 ) 가 사용되기 전에 한 번만 호출되며 클래스 객체를 초기화한다.
슈퍼 클래스의 initialize 메서드는 이처럼 호출되는 상황과 횟수가 정해져 있으므로 서브 클래스의 initialize 메서드에서 따로 호출해서는 안 된다.
-
어떤 클래스에 initialize 메서드를 구현하지 않으면 그 슈퍼 클래스는 initialize 메서드 호출을 두번(상속으로 한 번, 자기 자신이 한 번) 받는다.
따라서 initialize 메서드는 여러 번 호출되어도 문제가 발생하지 않도록 해야 한다.
+(void) initialize{
static BOOL nomore = NO;
if( nomore ){
return;
}
// do sth
nomore = YES;
}
* 4.5.6. 초기자 반환형
-
일반적으로 초기자 반환형은 id 형이어야 한다. ( 요즘은 instancetype )
-
클래스를 상속해서 사용할 때를 고려해서 클래스 정의에서는 정적인 형식을 남용할 것이 아니라 잘 생각해야 한다.
메서드 안에서 자기 클래스의 클래스 메서드를 호출할 때 다음처럼 써야 서브 클래스에서 이용하기에 좋다.
// [[Volume alloc] initWithMin:a max:b step:s]; // not recommended
[[[self class] alloc] initWithMin:a max:b step:s]; // recommended
'프로그래밍 놀이터 > iOS' 카테고리의 다른 글
[Objective-C] 선언 프로퍼티 (0) | 2017.12.28 |
---|---|
[Objective-C] 참조 카운터를 사용한 메모리 관리 방법 (0) | 2017.12.27 |
[Objective-C] 상속과 클래스 (0) | 2017.12.25 |
[Objective-C] Objective-C 프로그램 (0) | 2017.12.24 |
[Objective-C] 객체 기반 소프트웨어 작성 (0) | 2017.12.23 |
댓글