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

[Effective Objective-C] #42 performSelector 메서드군보다는 GCD 를 사용하라

by 돼지왕 왕돼지 2017. 10. 7.
반응형

 [Effective Objective-C] #42 performSelector 메서드군보다는 GCD 를 사용하라


출처 : Effective Objective-C

@selector, afterDelay, arc, dispatch_after, dispatch_get_main_queue, dispatch_time, DISPATCH_TIME_NOW, dispatch_time_t, float, GCD, Generic, id 타입, iNT, NSEC_PER_SEC, performSelector, performSelector:onThread:withObject:waitUntilDone:, performSelector:withObject:, performSelector:withObject:afterDelay:, performSelectorOnMainThread, performSelector may cause a leak because its selector is unknown, threading method, waitUntilDone, Warc-performSelector-leaks, withobject, [Effective Objective-C] #42 performSelector 메서드군보다는 GCD 를 사용하라, 동적 바인딩, 메모리 관리, 메모리 누수, 블록, 스레딩 메서드, 실행 시간 결정, 잠재적 버그, 최대 파라미터 갯수, 추가 부하, 컴파일러 경고


-

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 를 호출하는 방법을 이용하라.




반응형

댓글