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

[Effective Objective-C] #11 objc_msgSend 의 역할을 이해하라

by 돼지왕 왕돼지 2017. 8. 13.
반응형

 [Effective Objective-C] #11 objc_msgSend 의 역할을 이해하라


출처 : Effective Objective-C

objc_msgSend, objc_msgSendSuper, objc_msgSend_fpret, objc_msgSend_stret, receiver, SEL, selector, stack frame, [Effective Objective-C] #11 objc_msgSend 의 역할을 이해하라, 꼬리 호출 최적화, 동적 바인딩, 디버깅, 디스패치 시스템, 메시지, 메시지 포워딩, 백트레이스, 선택자, 셀렉터, 스택 프레임, 정적 바인딩, 추가 비용, 캐싱, 컴파일 시간, 하드코딩, 함수 주소


-

Objective-C 에서 많이 하는 일 중 하나는 객체의 메서드를 호출하는 것이다.

Objective-C 용어로는 메시지를 전달한다고 한다.

메시지는 이름 또는 선택자(selector)가 있다.

또 인자를 받고 값을 반환할 수도 있다.



-

Objective-C 가 C 를 포함하기 때문에 C 에서 사용하는 정적 바인딩과 동적 바인드 함수 호출을 이해하는 것부터 시작해야 한다.

정적 바인딩은 호출되는 함수가 컴파일 시간에 정해지는 것을 의미한다.

동적 바인딩은 다음과 같은 경우를 이야기한다.

호출될 함수를 실행 시간 전에는 알 수 없기 떄문이다.


void printHello(){

     // print hello

}


void printGoodbye(){

     // print goodbye

}


void doTheThing( int type ){

     void (*fnc)();

     if ( type == 0 ){

          fnc = printHello;

     } else{

          fnc = printGoodbye;

     }

     fnc(); // 이 시점에 어떤 것이 불릴지 확신이 없어 동적 바인딩이 된다.

     return 0;

}



-

동적 바인딩은 함수 주소가 하드코딩되어 있지 않아 함수의 주소를 찾아야 하는 추가적인 비용이 발생한다.



-

Objective-C 는 동적 바인딩 방식을 사용한다.

주어진 메시지에 호출될 함수를 정하는 것은 모두 실행 시간에 이루어지고 앱이 실행되는 도중 변경될 수도 있다.

이 능력이 Objective-C 를 완전한 동적 언어로 만들어준다.



-

id returnValue = [someObject messageName:paramter];


이 예제에서 someObject 는 receiver 이다.

그리고 messageName 은 selector 이다.

selector와 파라미터를 합쳐 메시지라고 한다.


메시지는 컴파일 시간에 표준 C 함수 호출로 변경된다.

이 함수가 바로 메시징의 핵심인 objc_msgSend 이고 함수의 프로토타입은 다음과 같다.


void objc_msgSend(id self, SEL cmd, …)


이 함수는 2개 이상의 파라미터를 받는 가변 인자 함수다.

첫 번째 파라미터는 리시버이고, 두번째 파라미터는 선택자다 (SEL 은 선택자 타입)

그리고 나머지 파라미터는 나타나는 순서대로 메시지 파라미터로 전달된다.

선택자는 메서드를 나타내는 이름이다.

선택자란 용어는 메서드로 대체해 사용할 수 있다.



-

obj_msgSend 함수는 리시버의 클래스가 구현한 메서드 목록을 살펴보고

선택자 이름과 일치하는 메서드를 발견하면 그 메서드로 건너뛴다.

찾지 못하면 함수는 상속 계층을 따라 메서드를 찾아 거꾸로 올라간다.

일치하는 메서드를 찾지 못하면 메시지 포워딩이 동작된다.



-

메서드가 호출될 때마다 많은 일이 일어나는 것처럼 보이지만 다행히 objc_msgSend 는 각 클래스마다 하나씩 있는 빠른 맵에 결과를 캐싱한다.

그래서 한 번 호출된 클래스의 선택자에 대한 메시징은 빠르게 수행된다.

이 빠른 경로는 정적으로 연결된 함수 호출에 비해 느리지만 캐싱된 선택자는 차이가 많이 나지 않는다.



-

Objective-C 런타임에는 다음과 같은 특정 상황을 처리할 수 있는 추가적인 함수들이 더 있다.


objc_msgSend_stret // 구조체를 반환하는 메시지를 보내기 위한 함수

objc_msgSend_fpret // 부동소수점을 반환하는 메시지를 보내기 위한 함수

objc_msgSendSuper // [super message:parameter] 와 같이 상위 클래스에 메시지를 보내기 위한 함수다.


objc_msgSend_stret 와 objc_msgSend_fpret 역시 상위로 호출하는 용도의 함수가 각각 있다.



-

꼬리 호출 최적화는 함수의 마지막 부분이 다른 함수를 호출할 때만 발생한다.

새로운 스택 프레임(stack frame)을 밀어 넣는 대신에 컴파일러는 코드를 다음 함수로 점프하게 만들 수 있다.

이는 오직 함수의 마지막 부분이 다른 함수를 호출하고 반환값이 없을 때만 가능하다.



-

실제로 우리가 Objective-C 코드를 작성할 때 이 모든 것을 걱정할 필요는 없지만 뒤편에서 무슨 일이 일어나는지 이해하고 있으면 좋다.

메시지가 호출되고 무슨 일이 일어나는지 이해하고 있으면 자신의 코드가 실행되는 것을 제대로 이해할 수 있고, 디버깅할 때 왜 objc_msgSend 가 백트레이스에서 항상 보이는지 알게 될 것이다.





기억할 점


메시지는 리시버, 선택자, 파라미터들로 구성된다.

메시지를 호출하는 것은 객체에 메서드를 호출하는 것과 동일하다.


호출을 할 때 모든 메시지는 동적 메시지 디스패치 시스템을 통해 실행된다.

이 시스템이 구현을 찾고 실행을 한다.




반응형

댓글