42. GCD를 많이 사용하고performSelector 시리즈를 적게 사용합니다

4478 단어
《고품질 iOS와 OS X 코드를 작성하는 52가지 효과적인 방법》-제6장 제42조(ps: 이것은 독서노트입니다. 기억을 깊이 있게 하고 참고만 제공)

제42조: GCD를 많이 사용하고performSelector 시리즈를 적게 사용하는 방법


Objective-C는 본질적으로 매우 동적 언어(제11조 참조)로 NSObject는 개발자가 임의로 어떤 방법을 호출할 수 있도록 몇 가지 방법을 정의했다.이 몇 가지 방법은 실행 방법의 호출을 연기할 수도 있고, 실행 방법에 사용할 라인을 지정할 수도 있다.이런 기능들은 원래 매우 유용했지만 대중추 파발과 블록 같은 신기술이 등장한 후에는 그다지 필요하지 않게 보였다.비록 몇몇 코드는 여전히 그것들을 자주 사용하지만, 필자는 너에게 피하는 것이 좋겠다고 충고한다.
이 중 가장 간단한 것은 "performSelector:"입니다.이 방법은 직접 선택 서브를 호출하는 것과 같은 효과가 있다.따라서 다음 두 줄 코드의 실행 효과는 같다.
[self performSelector:@selector(selectorName)];
[self selectorName];

이런 방식은 보기에 쓸데없는 것 같다.만약 선택자가 운행 기간에 결정된다면 이 방식의 강점을 나타낼 수 있을 것이다.이것은 동적 바인딩 위에 동적 바인딩을 다시 사용하는 것과 같으므로 다음과 같은 기능을 사용할 수 있습니다.
SEL selector;
if (/* some condition */) {
    selector = @selector(foo);
}else if (/* some other condition */){
    selector = @selector(bar);
}else{
    selector = @selector(baz);
}
[object performSelector:selector];

이런 프로그래밍 방식은 매우 유연해서 자주 복잡한 코드를 간소화하는 데 쓸 수 있다.선택자를 먼저 저장하고 어떤 사건이 발생한 후에 호출하는 방법도 있다.어떤 사용법을 사용하든지 간에 컴파일러는 실행할 선택자가 무엇인지 모른다. 이것은 실행 기간이 되어야 확정할 수 있다.그러나 이 기능을 사용하는 대가로 ARC에서 코드를 컴파일하면 컴파일러가 다음과 같은 경고 메시지를 보냅니다.
warning:PerformSelector may cause a leak because its selector is unknown

너는 아마 이런 경고가 나타날 것이라고 예상하지 못했을 것이다.이 소식은 이상하게 보일 수도 있고, 왜 메모리 유출 문제가 언급되었는지 궁금하다.왜냐하면 컴파일러가 호출할 선택자가 무엇인지 모르기 때문에 그 방법의 서명과 반환 값도 모르고 심지어 반환 값이 있는지도 모른다.그리고 컴파일러가 방법명을 모르기 때문에 ARC의 메모리 관리 규칙을 활용하여 반환값이 방출되어야 하는지 아닌지를 판정할 수 없다. 이를 감안하여 ARC는 비교적 신중한 방법으로 방출 조작을 추가하지 않는다.그러나 이렇게 하면 메모리 유출을 초래할 수 있다. 왜냐하면 방법은 대상을 되돌릴 때 이미 그것을 보류했기 때문이다.
다음 코드를 고려하십시오.
SEL selector;
if (/* some condition */) {
    selector = @selector(newObject);
}else if (/* some other condition */){
    selector = @selector(copy);
}else{
    selector = @selector(someProperty);
}
id ret = [object performSelector:selector];

만약 두 개의 선택자 중 하나를 호출한다면,ret 대상은 이 코드로 방출해야 하고, 세 번째 선택자라면 방출할 필요가 없다.ARC 환경에서뿐만 아니라 비 ARC 환경에서도 이렇게 해야 방법의 명명 규범을 엄격히 따랐다고 할 수 있다.ARC를 사용하지 않으면 (이 때 컴파일러도 경고 메시지를 보내지 않습니다) 앞의 두 가지 상황에서ret 대상을 수동으로 풀어야 하고, 뒤의 두 가지 상황에서는 풀어야 합니다.이 문제는 무시하기 쉬우며 정적 분석기를 사용해도 뒤이어 메모리 유출을 탐지하기 어렵다.performSelector 시리즈의 방법을 조심스럽게 사용해야 하는 이유 중 하나다.
이러한 방법은 그다지 이상적이지 않다. 또 다른 원인은 되돌아오는 값이void나 대상 유형일 뿐이라는 데 있다.실행할 선택자도void를 되돌릴 수 있지만,performSelector 방법의 되돌림 값 형식은 id입니다.정수나 부동점수 등 유형의 값을 되돌리려면 복잡한 변환 작업을 실행해야 하는데, 이런 변환은 틀리기 쉽다.id 유형은 임의의 Objective-C 대상을 가리키는 바늘을 표시하기 때문에 기술적으로 되돌아오는 값의 크기와 바늘이 차지하는 크기만 같으면 된다.반환 값의 유형이 C 언어의 구조체라면 performSelector 방법을 사용할 수 없습니다.
performSelector는 메시지를 보낼 때 매개 변수를 전달할 수 있는 다음과 같은 버전도 있습니다.
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

이런 방법들은 보기에는 유용한 것 같지만, 사실은 한계가 꽤 많다.매개 변수 형식이 id이기 때문에 들어오는 매개 변수는 대상이어야 합니다.만약 하위 수락의 매개 변수가 정수나 부동점수라면 이러한 방법을 사용할 수 없습니다.또한 하위 선택은 최대 두 개의 매개 변수만 받아들일 수 있지만, 매개 변수가 두 개가 아닌 경우, 대응하는performSelector 방법이 이 하위 선택을 실행할 수 없습니다.
performSelector 시리즈 방법은 선택자를 연기하거나 다른 라인에 놓고 실행할 수 있는 기능도 있습니다.
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);

그러나 이 방법들은 너무 제한적이라는 것을 곧 알게 될 것이다.만약 이러한 방법을 사용하려면 많은 매개 변수를 사전에 포장한 다음 호출된 방법에서 추출해야 한다. 그러면 비용이 증가하고 버그가 발생할 수도 있다.
만약 다른 대체 방안으로 바꾸면 이러한 제한을 받지 않을 것이다.가장 주요한 대체 방안은 블록을 사용하는 것이다(제37조 참조).그리고performSelector 시리즈 방법이 제공하는 스레드 기능은 모두 대중추 발송 메커니즘에서 블록을 사용하여 실현할 수 있다.지연 실행은 dispatchafter는 다른 라인에서 작업을 수행하면dispatchsync 및 dispatchasync로 이루어집니다.
예를 들어, 다음 작업을 연기합니다.
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));

dispatch_after(time, dispatch_get_main_queue(), ^{
    [self doSomething];
});

주 스레드에서 작업을 수행하려면 다음과 같이 하십시오.
dispatch_async(dispatch_get_main_queue(), ^{
    [self doSomething];
});

요점


4
  • performSelector 시리즈 방법은 메모리 관리에 소홀하기 쉽다.이것은 실행할 선택 하위 항목이 무엇인지 확정할 수 없기 때문에 ARC 컴파일러도 적당한 메모리 관리 방법을 삽입할 수 없습니다

  • 4
  • performSelector 시리즈 방법에서 처리할 수 있는 선택자는 너무 제한되어 있으며, 선택자의 반환값 유형과 발송 방법의 매개 변수 개수는 모두 제한을 받는다

  • 4
  • 임무를 다른 라인에 놓고 집행하려면perform Selector 시리즈 방법을 사용하지 않고 임무를 블록에 봉한 다음에 대중추 발송 메커니즘과 관련된 방법을 사용해서 실현하는 것이 좋다
  • 좋은 웹페이지 즐겨찾기