momodudu.zip

Objective-C의 메세지 전송방법(함수 호출 방법) 본문

ios/Objective-C

Objective-C의 메세지 전송방법(함수 호출 방법)

ally10 2019. 12. 29. 17:03

아 정말 이거때문에 옵씨 코드를 처음 접했을때 진짜 멘붕이었다... 맨날 닷 신택스로 메소드를 불러왔던 나로서는

별 희안하게 메소드를 호출하는 옵씨 문법에 적응하기 진짜 힘들었다... 

 

Objective-C는 일반적으로 이제까지 내가 생각해왔던 '함수 호출'의 개념을 '메세지 전송' 이라고 부른다.

 

c++을 예로 들면, 어떤 객체에 대한 메소드를 instance.function(paramter)의 형식으로 호출하게 되면 instance에 딸리는 메소드를 호출하기 전에 현재까지 진행한 곳의 주소를 스택에 넣어놓고 함수를 처리한 후 다시 원래 루틴으로 돌아온다.

 

Objective-C의 경우, 어떤 메소드를 호출하게 되는 이 과정은 3가지 구성요소로 이루어진다.

sender/receiver/message. sender는 함수를 호출하는 대상을 말하고, receiver는 이 함수를 처리할 대상을 말한다.

message는 이 처리과정을 담은 메세지 그 자체라고 보면 된다. 그래서 selector라는 개념이 나오기 시작하는데, 일종의 함수 이름을 담은 메세지라고 보면 된다.

 

[ object method ]

실제로 옵씨의 함수 호출은 위와 같은 코드를 자주 쓰는데, 이를 컴파일러가 아래와 같이 해석한다.

objc_msgSend(object, method)

 

이는 objc_msgSend함수에서 receiver인 object instance에서 isa값을 통해 실제 클래스를 찾고, 그 클래스 내의 dispatch table을 통해 셀렉터로 넘어온 method가 있는지 확인한다. (만약 없다면 super class까지 올라간다고 한다.)

해당 클래스 테이블에 method가 존재하면 인자와 함께 프로시저를 호출하여 이후에는 C와 동일한 방법으로 메소드가 호출된다.

 

 

그리고 이 메세지 전송 방법은 여러가지가 있는데, 그중에서 일단 오늘은 두가지만 본다.

 

1. NSObject class의 - performSelector

2. NSInvocation 객체 이용

 

예제를 통해서 각 종류의 호출이 어떻게 이루어지는지 보자.

 

// Address.h
@interface Address : NSObject{
    int zipCode;
}

-(void)findZipCode:(NSString*) address;

@end


// Apartment.h
@interface Apartment:NSObject{
    Address* address;
}

/// 주소를 가져온다.
/// - parameter address: address
- (void)setApartmentAddress:(NSString*)addressStr;

@property int numOfRooms;

@end

대충 위와 같은 코드가 있다고 가정하자.. 티스토리에서 옵씨 코드블럭을 지원 안하는건 처음알았다.... -ㅅ-

 

Apartment의 address property에 address string을 전달해서 zipcode를 찾아내는 findZipCode라는 메소드를 호출하고자 한다.

즉 sender는 Apartment instance이고, receiver는 Address가 될 것 이다.

 

1. performSelector

- (void) setApartmentAddres:(NSString *)addressStr
{
    if( [address respondsToSelector:@selector(findZipCode)] )
    {
        [address performSelector:@selector(findZipCode) withObject:addressStr];
    }
}

NSObject의 respondToSelector는 인자로 받는 selector가 address instandce에 implement되어있는지 확인하는 함수다.

만약 구현이 되지 않고 셀렉터를 호출할 경우 죽는다고 합니다 (...)

 

그래서 메소드가 구현이 되어있는지 확인 후, 인자인 NSString* address와 함께 넘겨준다.

다만 이 인자에는 약간 제한이있다. 2가지밖에 전달하지 못하고, 전달하는 인자 타입이 id라서 클래스 인스턴스만 넘길 수 있다.

즉 int나 float같은 primitive는 넘기지 못한다고 한다.

 

그 외에도 performSelector의 변형으로 지연시간을 주거나 하는 함수도 있다.

 

사실 위 코드는 밑에서 설명하겠지만, [address findZipCode:address] , C++ style로 설명하자면 address.findZipCode(address)와 똑같은 기능을 한다. 

이 performSelector의 멋진점은, 일종의 동적바인딩과 같은 기능을 할 수 있다는점에 있다. 아래 코드를 보자.

 

- (void) setApartmentAddres:(NSString *)addressStr
{
    SEL zipCodeSelector = NSSelectorFromString(@"findZipCode");
    if( [countryCode compare:@"KOR"] )
    {
        zipCodeSelector = NSSelectorFromString(@"findZipCodeForKorea");
    }

    if( [address respondsToSelector:zipCodeSelector] )
    {
        [address performSelector:zipCodeSelector withObject:addressStr];
    }
}

Apartment 클래스의 countryCode property에 따라서 findZipCode함수를 다르게 지정해 줄 수 있다.

위 코드에서는 countryCode 가 KOR인 경우, findZipCode가 아닌 findZipCodeForKorea 를 호출하도록 하였다.

즉, selector를 코드 단계에서 조건에 따라 지정해줄 수 있고, 컴파일러 단에서는 이 코드에서 무슨 함수가 수행될지 모르게 할 수 있기 때문에 일종의 동적바인딩 같은 역할을 할 수 있다.

 

2. NSInvocation

- (void) setApartmentAddres:(NSString *)addressStr
{
    SEL typeSelector = NSSelectorFromString(@"findZipCode");
    if( [address respondsToSelector:typeSelector] )
    {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:
                                    [[address class] instanceMethodSignatureForSelector:typeSelector]];
        [invocation setSelector:typeSelector];
        [invocation setTarget:address];
        [invocation setArgument:&addressStr atIndex:2];
        [invocation invoke];
    }
}

두번째는 NSInvocation이다. 이건 좀 복잡한데, NSInvocation이라는 객체를 만들어서 타겟, 셀렉터를 설정해서 타겟에게 셀렉터를 수행해라.라고 말그대로 invoke를 날리는 것이다.

 

그래서 먼저 NSInvocation instance를 만들 때 보면, methodSignature라는것을 먼저 만들어주는데, 이건 단지 함수의 외형만을 설명하는 말그대로 시그니쳐다.

즉, 함수 이름이나 파라미터등 함수의 외형만 정의되어있고 특정 메소드와 연결된게 아니다. 그래서 selector와는 엄연히 다르다고 할 수 있다.

 

그리고나서 셀렉터와 타겟, paramter를 정의해준다. paratmer의 경우 순서대로 정의해주고 파라미터의 인덱스를 정의해주어야 하는데

이 파라미터 인덱스는 2부터 시작한다. 0,1은 각각 self와 셀렉터로 designated되어있기 때문이다.

 

그리고나서 invoke를 실행하게 되면 실제로 이 함수가 수행된다. 또한 리턴 값이있는 함수일경우 getReturnValue method로 리턴값을 가져올수도 있다.