본문 바로가기
프로그래밍 놀이터/iOS

[Objective-C] 객체 형식과 동적 결합

by 돼지왕 왕돼지 2017. 12. 26.
반응형

 [Objective-C] 객체 형식과 동적 결합


출처 : OS X 구조를 이해하면서 배우는 Objective-C Chap 4.

0, >, @class, @package, @private, @protected, @public, char, clang, class object, class 형, Cocoa, definition overloading, dynamic binding, forward declaration, ID, id형, initialize, initialize 여러번 호출, instancetype, isMemberOfClass, Mac OS X, nil, NSMethodSignature, nsobject, nsobject*, overloading, overloading 이 없다, polymorphism, Self, Signature, subclass, void *, [Objective-C] 객체 형식과 동적 결합, 가시성, 객체, 객체 형식과 동적 결합, 구조체, 구현부, 구현부 기본 @private, 다중 정의, 다형성, 동일 인스턴스, 동적 결합, 동적 결합이란, 동적 구속, 동적 바인딩, 메서드 서명, 빈 포인터 nil, 사용되기 전, 서명, 서명이 다를 때, 세미콜론, 슈퍼 클래스, 실수, 인스턴스 변수에 접근, 인스턴스 변수의 가시성, 인스턴스 변수의 정보 은폐, 전역 변수, 접근자, 정수, 정적 결합, 정적 바인딩, 중첩, 초기자 반환형, 컴파일 속도 향상, 클래스 객체, 클래스 객체 초기화, 클래스 객체 형식, 클래스 메서드, 클래스 변수, 클래스 생성 시기, 클래스 전방 선언, 파일 내부, 포인터, 프로그래밍과 형식 선언, 프로그램 실행, 형식과 클래스, 형식의 정적 확인, 호출 금지


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





반응형

댓글