[Effective Objective-C] #42 performSelector 메서드군보다는 GCD 를 사용하라
출처 : Effective Objective-C
-
performSelector 는 다음과 같이 사용한다.
- (id)performSelector:(SEL)selector
이 메서드를 통해 선택자를 호출하는 것과 그냥 선택자를 직접 호출하는 것은 기능적으로 같은 것이다.
즉, 아래 두 코드는 같은 내용이다.
[object performSelector:@selector(selectorName)];
[object selectorName];
-
이 메서드들의 진정한 능력은 선택자를 실행 시간에 결정할 수 있는 데서 비롯된다.
이러한 동적 바인딩의 가장 큰 능력은 다음과 같은 일을 할 수 있다는 것이다.
SEL selector;
if ( /* 조건 */ ){
selector = @selector(foo);
} else if ( /* 다른 조건 */ ){
selector = @selector(bar);
} else{
selector = @selector(baz);
}
[object performSelector:selector];
이 코드는 매우 유연하다.
그리고 복잡한 코드를 간략하게 만드는 데 가끔 사용된다.
또한 이 기능은 이벤트가 발생한 후에 수행되어야 하는 선택자를 저장하는 데도 사용된다.
여하튼 컴파일러는 실행 시간에 어떤 선택자가 수행될지 알 수 없다.
-
이기능을 ARC를 사용하는 코드에 사용하면 컴파일러가 다음과 같이 경고하는 것을 감수해야 한다.
warning: performSelector may cause a leak because its selector is unknown [-Warc-performSelector-leaks]
컴파일러가 어떤 선택자가 호출될 것인지 모르기 때문이다.
따라서 메서드 시그너처와 리턴 타입을 모를 뿐 아니라 리턴 값이 있는지조차 모른다.
또한 컴파일러는 메서드 이름도 모른다.
그렇기 때문에 반환 값을 릴리스해야 하는지를 결정하는 ARC 의 메모리 관리 규칙을 적용할 수 없다.
이러한 이유 때문에 ARC 는 이 메서드들을 안전하게 실행하고 릴리스를 추가하지 않는다.
그러나 그 결과로 메모리 누수가 발생할 수 있다.
반환되는 객체가 리테인된 객체일 수 있기 때문이다.
-
다음과 같은 추가적인 파라미터를 전달할 수 있는 몇 가지 performSelector 의 변종(variant)도 있다.
-(id)performSelector:(SEL)selector withObject:(id)object
-(id)performSelector:(SEL)selector withObject:(id)objectA withObject:(id)objectB
-
이 메서드들은 유용해 보이지만 사용할 수 있는 곳이 매우 한정적이다.
전달할 객체는 항상 id 타입의 객체여야만 한다.
그래서 int 나 float 을 파라미터로 받는 선택자는 이 메서드들을 이용할 수 없다.
게다가 performSelector:withObject:withObject: 메서드를 사용하는 선택자는 최대 두 개의 파라미터만 받을 수 있다.
두 개 이상의 파라미터를 받아 선택자를 수행할 수 있는 메서드는 없다.
-
performSelector 메서드군의 또 다른 기능 중 하나는 선택자를 지연해서 실행하거나 다른 스레드에서 실행할 수 있는 것이다.
이러한 메서드들 중 자주 사용되는 것들은 다음과 같다.
- (void)performSelector:(SEL)selector withObject:(id)argument afterDelay:(NSTimeInterval)delay
- (void)performSelector:(SEL)selector onThread:(NSThread*)thread withObject:(id)argument waitUntilDone:(BOOL)wait
- (void)performSelectorOnMainThread:(SEL)selector withObject:(id)argument waitUntilDone:(BOOL)wait
그러나 이러한 메서드는 금방 사용에 제약이 많이 생기게 된다.
실행이 지연된 후에 주어진 선택자를 두 개의 인자로 실행하는 메서드가 없다.
이 같은 이유로 스레딩 메서드(threading method)는 포괄적(generic)으로 사용할 수 있는 경우가 거의 없다.
이러한 메서드를 사용하길 원하는 코드는 때때로 인자들을 사전에 넣고 호출된 메서드에서 꺼내서 쓴다.
하지만 그렇게 하면 추가적인 부하가 발생하고 잠재적인 버그가 생기기 쉽다.
-
위와 같은 모든 제약은 다음 대안 중 하나를 이용해 해결할 수 있다.
가장 흔히 쓰는 대안은 블록을 사용하는 것이다.
게다가 GCD 와 함께 블록을 사용하면 performSelector 메서드들을 사용할 때 생기는 스레드 관련 문제를 해결해준다.
-
메서드를 지연 후 실행하게 하는 것은 dispatch_after 를 이용하면 된다.
그리고 다른 스레드에서 메서드를 수행하는 것은 dispatch_sync 와 dispatch_async 를 이용해 할 수 있다.
dispatch_after 는..
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^(void){
[self doSomething];
});
메인 스레드에서 작업 수행은..
dispatch_async(dispatch_get_main_queue(), ^{
[self doSomething];
});
기억할 점
-
performSelector 메서드군을 사용하면 메모리 관리가 어렵다.
어떤 선택자가 실행될 것인지 알 수 없다면 ARC 컴파일러는 적절한 메모리 관리 호출을 코드에 삽입할 수 없을 것이다.
-
이 메서드군은 반환 타입과 메서드에 전달할 수 있는 파라미터 개수가 매우 한정적이다.
-
선택자를 다른 스레드에서 실행하기 위해서는 블록을 사용하는 GCD 를 호출하는 방법을 이용하라.
댓글