selectSelect를 알 수 없으므로 performSelector에서 누수가 발생할 수 있습니다.


1258

ARC 컴파일러에서 다음과 같은 경고 메시지가 나타납니다.

"performSelector may cause a leak because its selector is unknown".

내가하고있는 일은 다음과 같습니다.

[_controller performSelector:NSSelectorFromString(@"someMethod")];

왜이 경고가 나타 납니까? 컴파일러가 선택기가 존재하는지 여부를 확인할 수는 없지만 누출을 일으키는 이유는 무엇입니까? 더 이상이 경고가 표시되지 않도록 코드를 변경하려면 어떻게해야합니까?


3
변수의 이름은 동적이며 다른 많은 것들에 달려 있습니다. 존재하지 않는 것을 호출 할 위험이 있지만 문제는 아닙니다.
Eduardo Scoz

6
@ matt 왜 객체에서 메소드를 동적으로 호출하는 것이 나쁜 습관입니까? NSSelectorFromString ()의 전체 목적이이 방법을 지원하지 않습니까?
Eduardo Scoz

7
performSelector를 통해 설정하기 전에 [_controller respondsToSelector : mySelector]를 테스트해야합니다.
mattacular

50
@mattacular Wish 투표 할 수 있습니다 : "그건 나쁜 습관입니다."
ctpenrose

6
문자열이 리터럴이라는 것을 알고 있다면 @selector ()를 사용하면 컴파일러가 선택기 이름을 알 수 있습니다. 실제 코드가 런타임에 구성되거나 제공된 문자열로 NSSelectorFromString ()을 호출하는 경우 NSSelectorFromString ()을 사용해야합니다.
Chris 페이지

답변:


1211

해결책

컴파일러는 이에 대해 경고하고 있습니다. 이 경고를 무시해야하는 경우는 매우 드물며 해결하기 쉽습니다. 방법은 다음과 같습니다.

if (!_controller) { return; }
SEL selector = NSSelectorFromString(@"someMethod");
IMP imp = [_controller methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(_controller, selector);

또는 더 끔찍하게 (가드없이 읽기가 어렵지만) :

SEL selector = NSSelectorFromString(@"someMethod");
((void (*)(id, SEL))[_controller methodForSelector:selector])(_controller, selector);

설명

여기서 진행중인 것은 컨트롤러에 해당하는 메소드에 대한 C 함수 포인터를 컨트롤러에 요청하는 것입니다. 모든 NSObject응답에 응답 methodForSelector:하지만 class_getMethodImplementationObjective-C 런타임 에서도 사용할 수 있습니다 (와 같은 프로토콜 참조 만있는 경우 유용 id<SomeProto>). 이 함수 포인터를 IMPs 라고 하며 간단한 typedef함수 포인터 ( id (*IMP)(id, SEL, ...)) 1 입니다. 이것은 메소드의 실제 메소드 서명에 가깝지만 항상 정확하게 일치하지는 않습니다.

를 가지고 나면 IMPARC에 필요한 모든 세부 사항 (두 개의 암시 적 숨겨진 인수 self_cmd모든 Objective-C 메서드 호출 포함) 이 포함 된 함수 포인터로 캐스트해야합니다 . 이것은 세 번째 줄에서 처리됩니다 ( (void *)오른쪽에있는 포인터는 포인터 유형이 일치하지 않으므로 수행중인 작업을 알고 경고를 생성하지 않음을 컴파일러에게 알려줍니다).

마지막으로 함수 포인터 2 를 호출합니다 .

복잡한 예

선택자가 인수를 받거나 값을 반환하면 약간 변경해야합니다.

SEL selector = NSSelectorFromString(@"processRegion:ofView:");
IMP imp = [_controller methodForSelector:selector];
CGRect (*func)(id, SEL, CGRect, UIView *) = (void *)imp;
CGRect result = _controller ?
  func(_controller, selector, someRect, someView) : CGRectZero;

경고에 대한 추론

이 경고의 이유는 ARC를 사용하면 런타임에서 호출하는 메소드의 결과로 수행 할 작업을 알아야하기 때문입니다. 그 결과는 무엇이든 될 수있다 : void, int, char, NSString *, id, 등 ARC는 일반적으로 작업중인 개체 형식의 헤더에서이 정보를 가져옵니다.

ARC는 반환 값을 고려할 것이라고에만 4 일이 정말있다 : 4

  1. 무시가 아닌 개체 유형 ( void, int, 등)
  2. 객체 값을 유지 한 다음 더 이상 사용하지 않을 때 해제합니다 (표준 가정)
  3. 더 이상 사용하지 않을 때 새 객체 값을 해제합니다 ( init/ copy패밀리의 메소드 또는 로 표시된 메소드 ns_returns_retained).
  4. 아무것도 수행하지 않고 반환 된 객체 값이 로컬 범위에서 유효하다고 가정합니다 (가장 큰 릴리스 풀이 배수 될 때까지 ns_returns_autoreleased)

에 대한 호출은 methodForSelector:이 부르고 메소드의 반환 값이 대상이지만, 해제 / 유지하지 않는 것으로 가정합니다. 따라서 위의 # 3에서와 같이 객체가 해제되어야하는 경우 누출을 일으킬 수 있습니다 (즉, 호출하는 메소드가 새 객체를 반환합니다).

해당 반환 void또는 다른 객체 이외의 객체 를 호출하려는 선택기의 경우 컴파일러 기능을 사용하여 경고를 무시할 수는 있지만 위험 할 수 있습니다. Clang이 로컬 변수에 할당되지 않은 반환 값을 처리하는 방법에 대한 몇 가지 반복을 살펴 보았습니다. ARC가 활성화되어 methodForSelector:있어도 사용하지 않으려 는 경우에도 반환 된 객체 값을 유지 및 해제 할 수없는 이유 는 없습니다. 컴파일러의 관점에서 볼 때 결국 객체입니다. 즉, 호출하는 메소드가 someMethod객체를 포함하여 비 객체를 반환 void하면 가비지 포인터 값이 유지 / 해제되고 충돌 할 수 있습니다.

추가 인수

한 가지 고려 사항은 이것이 동일한 경고와 함께 발생하며 performSelector:withObject:해당 메소드가 매개 변수를 사용하는 방법을 선언하지 않으면 비슷한 문제 가 발생할 수 있다는 것입니다. ARC를 사용하면 소비 된 매개 변수 를 선언 할 수 있으며 메서드가 매개 변수를 사용하는 경우 결국 좀비에게 메시지를 보내 충돌이 발생할 수 있습니다. 브리지 캐스팅으로이 문제를 해결할 수있는 방법이 있지만 실제로 IMP위 의 and 함수 포인터 방법을 사용하는 것이 좋습니다 . 소비되는 매개 변수는 거의 문제가되지 않으므로이 문제는 발생하지 않습니다.

정적 선택기

흥미롭게도 컴파일러는 정적으로 선언 된 선택기에 대해 불평하지 않습니다.

[_controller performSelector:@selector(someMethod)];

그 이유는 컴파일러가 실제로 컴파일하는 동안 선택기와 객체에 대한 모든 정보를 기록 할 수 있기 때문입니다. 아무것도 추측 할 필요가 없습니다. (저는 1 년 전에 출처를 확인하여 확인했지만 지금은 참조 자료가 없습니다.)

억압

이 경고의 억제가 필요한 상황과 코드 디자인이 좋은 상황을 생각할 때 나는 비어 있습니다. 누군가이 경고음이 필요했던 경험이 있다면 공유하십시오. (위의 내용이 제대로 처리되지 않습니다.)

NSMethodInvocation이것을 처리 하기 위해를 구축하는 것도 가능 하지만 그렇게하려면 더 많은 타이핑이 필요하고 느리기 때문에 그렇게 할 이유가 거의 없습니다.

역사

performSelector:메소드 계열이 처음 Objective-C에 추가 되었을 때 ARC는 존재하지 않았습니다. ARC를 작성하는 동안 Apple은 개발자가 명명 된 선택기를 통해 임의의 메시지를 보낼 때 메모리를 처리하는 방법을 명시 적으로 정의하는 다른 방법을 사용하도록 안내하는 방법으로 이러한 방법에 대해 경고를 생성하기로 결정했습니다. Objective-C에서 개발자는 원시 함수 포인터에서 C 스타일 캐스트를 사용하여이를 수행 할 수 있습니다.

Swift가 도입되면서 Apple performSelector: 여러 가지 방법을 "본질적으로 안전하지 않은"것으로 문서화 했으며 Swift에서는 사용할 수 없습니다.

시간이 지남에 따라 우리는 이러한 진행을 보았습니다.

  1. 초기 버전의 Objective-C 허용 performSelector:(수동 메모리 관리)
  2. ARC가있는 Objective-C는 다음을 사용하도록 경고합니다. performSelector:
  3. Swift는 performSelector:이러한 방법을 "본질적으로 안전하지 않은" 것으로 접근 및 문서화 할 수 없습니다.

그러나 명명 된 선택기를 기반으로 메시지를 전송한다는 아이디어는 "본질적으로 안전하지 않은"기능이 아닙니다. 이 아이디어는 Objective-C와 다른 많은 프로그래밍 언어에서 오랫동안 성공적으로 사용되었습니다.


1 모든 목표 - C 방법은 두 가지 숨겨진 인수를, self그리고 _cmd당신이 메서드를 호출 할 때 그 암시 적으로 추가됩니다.

2NULL C에서 함수 호출 은 안전하지 않습니다. 컨트롤러가 있는지 확인하는 데 사용되는 가드는 우리에게 물체가 있는지 확인합니다. 따라서 우리는 우리가 얻을 것이다 알고 IMP에서 methodForSelector:(이있을 수 있지만 _objc_msgForward, 메시지 전달 시스템에 입력)를. 기본적으로 경비원을 배치하면 전화를 걸 수있는 기능이 있다는 것을 알고 있습니다.

3 실제로 객체를 선언 id하고 모든 헤더를 가져 오지 않으면 잘못된 정보를 얻을 수 있습니다. 컴파일러가 생각하는 코드 충돌이 발생할 수 있습니다. 이것은 매우 드물지만 발생할 수 있습니다. 일반적으로 두 가지 메소드 서명 중 어느 것을 선택할지 모른다는 경고가 나타납니다.

4 자세한 내용은 유지 반환 값유지 되지 않은 반환 값에 대한 ARC 참조 를 참조하십시오.


@wbyoung 코드가 유지 문제를 해결하면 왜 performSelector:메소드가 이런 식으로 구현되지 않는지 궁금 합니다. 그것들은 엄격한 메소드 서명 (returning id, 하나 또는 두 ids를 취함 )을 가지 므로 기본 유형을 처리 할 필요가 없습니다.
Tricertops 2013

1
@Andy 인수는 메소드 프로토 타입의 정의에 따라 처리됩니다 (유지 / 해제되지 않음). 관심사는 주로 반품 유형을 기반으로합니다.
wbyoung

2
Cannot initialize a variable of type 'CGRect (*)(__strong id, SEL, CGRect, UIView *__strong)' with an rvalue of type 'void *'최신 Xcode를 사용할 때 "복잡한 예"는 오류를 발생시킵니다 . (5.1.1) 그래도 나는 많은 것을 배웠다!
스탠 제임스

2
void (*func)(id, SEL) = (void *)imp;컴파일하지 않고 다음으로 대체했습니다.void (*func)(id, SEL) = (void (*)(id, SEL))imp;
Davyd Geyl

1
변화 void (*func)(id, SEL) = (void *)imp;<…> = (void (*))imp;<…> = (void (*) (id, SEL))imp;
Isaak 오시 Dunayevsky

1182

Xcode 4.2의 LLVM 3.0 컴파일러에서 다음과 같이 경고를 표시하지 않을 수 있습니다.

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self.ticketTarget performSelector: self.ticketAction withObject: self];
#pragma clang diagnostic pop

여러 곳에서 오류가 발생하고 C 매크로 시스템을 사용하여 pragma를 숨기려면 매크로를 정의하여 경고를 더 쉽게 억제 할 수 있습니다.

#define SuppressPerformSelectorLeakWarning(Stuff) \
    do { \
        _Pragma("clang diagnostic push") \
        _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
        Stuff; \
        _Pragma("clang diagnostic pop") \
    } while (0)

다음과 같이 매크로를 사용할 수 있습니다.

SuppressPerformSelectorLeakWarning(
    [_target performSelector:_action withObject:self]
);

수행 된 메시지의 결과가 필요한 경우 다음을 수행 할 수 있습니다.

id result;
SuppressPerformSelectorLeakWarning(
    result = [_target performSelector:_action withObject:self]
);

최적화가 없음 이외의 것으로 설정된 경우이 방법으로 메모리 누수가 발생할 수 있습니다.
Eric

4
@Eric 아니요 "initSomething"또는 "newSomething"또는 "somethingCopy"와 같은 재미있는 메소드를 호출하지 않는 한 불가능합니다.
Andrey Tarantsov

3
@Julian 그것은 작동하지만 전체 파일에 대한 경고를 해제합니다 – 필요하거나 원하지 않을 수도 있습니다. 함께 Wrappping poppush-pragmas 훨씬 깨끗하고 안전합니다.
Emil

2
이 모든 것은 컴파일러를 침묵시킵니다. 이렇게해도 문제가 해결되지 않습니다. 선택기가 존재하지 않으면 거의 망할 수 있습니다.
Andra Todorescu

2
이는 if ([_target respondsToSelector:_selector]) {유사한 로직으로 랩핑 된 경우에만 사용해야합니다 .

208

이것에 대한 나의 추측은 이것이다 : 선택기는 컴파일러에게 알려지지 않기 때문에 ARC는 적절한 메모리 관리를 시행 할 수 없다.

실제로 메모리 관리가 특정 규칙에 의해 메소드의 이름과 연결되는 경우가 있습니다. 특히, 편리한 생성자make 메소드를 생각 하고 있습니다. 전자는 협약에 의해 자동 해제 된 객체를 반환한다. 후자는 보유 된 물체이다. 이 규칙은 선택기의 이름을 기반으로하므로 컴파일러가 선택기를 모르면 적절한 메모리 관리 규칙을 적용 할 수 없습니다.

이것이 정확하다면 메모리 관리와 관련하여 모든 것이 정상인지 확인하는 한 (예 : 메소드가 할당 한 객체를 반환하지 않는 경우) 코드를 안전하게 사용할 수 있다고 생각합니다.


5
답변 주셔서 감사합니다, 나는 무슨 일이 일어나고 있는지 좀 더 살펴 보겠습니다. 그래도 경고를 무시하고 사라지게하는 방법에 대한 아이디어가 있습니까? 안전한 호출이 무엇인지 경고가 코드에 영원히 저장되는 것을 싫어합니다.
Eduardo Scoz

84
그래서 나는 애플 포럼에서 누군가에게 이것이 사실이라는 확인을 받았습니다. 향후 릴리스에서 사람들이이 경고를 비활성화 할 수 있도록 잊어 버린 재정의를 추가 할 것입니다. 감사.
Eduardo Scoz

5
이 답변은 ARC가 컨벤션 및 메소드 이름을 기반으로 무언가를 언제 릴리스 할 것인지를 결정하려는 경우 어떻게 "참조 횟수"입니까? ARC가 코드가 어떤 규칙을 따르더라도 참조를 실제로 추적하지 않고 특정 규칙을 따르는 것으로 가정하는 경우 설명하는 동작은 완전히 임의적 인 것보다 조금 더 나을뿐입니다.
aroth

8
ARC는 컴파일시 유지 및 릴리스를 추가하는 프로세스를 자동화합니다. 가비지 콜렉션이 아닙니다 (그래서 엄청나게 빠르고 오버 헤드가 낮은 이유입니다). 전혀 임의적이지 않습니다. 기본 규칙은 수십 년 동안 일관되게 적용 되어온 확립 된 ObjC 규칙을 기반으로합니다. 이를 __attribute통해 메모리 관리를 설명하는 모든 메소드 에 명시 적으로 추가 할 필요가 없습니다 . 그러나 컴파일러가이 패턴 (매우 일반적 이었지만 최근에는 더 강력한 패턴으로 대체 된 패턴)을 올바르게 처리 할 수 ​​없습니다.
Rob Napier

8
그래서 우리는 더 이상 ivar 유형을 가질 수 없으며 SEL상황에 따라 다른 선택기를 할당 할 수 없습니까? 갈 길, 역동적 인 언어 ...
Nicolas Miari

121

프로젝트에 빌드 설정 , 아래에 다른 경고 플래그 ( WARNING_CFLAGS), 추가
-Wno-arc-performSelector-leaks

이제 호출하는 선택기가 객체를 유지하거나 복사하지 않도록하십시오.


12
전체 프로젝트가 아닌 특정 파일에 대해 동일한 플래그를 추가 할 수 있습니다. 빌드 단계-> 컴파일 소스 아래에서 파일 당 컴파일러 플래그를 설정할 수 있습니다 (ARC에서 파일을 제외하기 위해 수행하려는 것처럼). 내 프로젝트에서 하나의 파일 만이이 방법을 사용하여 선택기를 사용해야하므로 제외하고 다른 파일은 그대로 두었습니다.
Michael

111

컴파일러가 경고를 재정의 할 수있을 때까지 해결 방법으로 런타임을 사용할 수 있습니다.

objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));

대신에

[_controller performSelector:NSSelectorFromString(@"someMethod")];

당신은해야합니다

#import <objc/message.h>


8
ARC는 Cocoa 규칙을 인식 한 다음 해당 규칙을 기반으로 보유 및 릴리스를 추가합니다. C는 이러한 규칙을 따르지 않기 때문에 ARC는 수동 메모리 관리 기술을 사용하도록합니다. CF 오브젝트를 작성하는 경우 CFRelease ()해야합니다. dispatch_queue_create () 인 경우 dispatch_release ()해야합니다. 결론적으로 ARC 경고를 피하려면 C 객체와 수동 메모리 관리를 사용하여 경고를 피할 수 있습니다. 또한 해당 파일에서 -fno-objc-arc 컴파일러 플래그를 사용하여 파일별로 ARC를 비활성화 할 수 있습니다.
jluckyiv

8
캐스팅 없이는 할 수 없습니다. Varargs는 명시 적으로 입력 된 인수 목록과 다릅니다. 일반적으로 우연의 일치로 작동하지만 "우연의 일치"는 올바른 것으로 간주하지 않습니다.
bbum

21
그렇게하지 마십시오 [_controller performSelector:NSSelectorFromString(@"someMethod")];objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));동등하지 않습니다! Methodive- Cast MismatchesObjective-C의 약한 타이핑의 큰 약점을 살펴보면 문제를 깊이있게 설명합니다.
0xced

5
@ 0xced이 경우 괜찮습니다. objc_msgSend는 객체를 매개 변수로만 가져 오기 때문에 performSelector : 또는 해당 변형에서 올바르게 작동 한 선택기에 대해 메소드 서명 불일치를 작성하지 않습니다. 모든 매개 변수가 포인터 (객체 포함), double 및 NSInteger / long이고 반환 유형이 void, pointer 또는 long이면 objc_msgSend가 올바르게 작동합니다.
매트 Gallagher

88

수행 선택기가있는 파일에서만 오류를 무시하려면 다음과 같이 #pragma를 추가하십시오.

#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

이것은이 줄의 경고를 무시하지만 여전히 프로젝트의 나머지 부분에서 경고를 허용합니다.


6
문제가 발생한 방법 직후에 경고를 다시 켤 수 있습니다 #pragma clang diagnostic warning "-Warc-performSelector-leaks". 경고를 끄면 가능한 빨리 다시 켜는 것을 좋아하므로 예기치 않은 다른 경고가 실수로 빠지지 않도록하십시오. 이것이 문제가 될 가능성은 없지만 경고를 해제 할 때마다 내 연습 일뿐입니다.
Rob

2
#pragma clang diagnostic warning push변경하기 전에를 사용하고 이전 상태를 복원하여 이전 컴파일러 구성 상태를 복원 할 수도 있습니다 #pragma clang diagnostic warning pop. 로드를 끄고 코드에 많은 재 활성화 프라 그마 라인을 원하지 않는 경우에 유용합니다.
deanWombourne

다음 줄만 무시합니까?
hfossli

70

이상하지만 참 : 허용되는 경우 (예 : 결과가 무효이고 runloop주기를 한 번 신경 쓰지 않아도 됨) 이것이 0 인 경우에도 지연을 추가하십시오.

[_controller performSelector:NSSelectorFromString(@"someMethod")
    withObject:nil
    afterDelay:0];

이것은 컴파일러가 객체를 반환 할 수없고 잘못 관리 할 수 ​​없음을 보장하기 때문에 경고를 제거합니다.


2
이것이 실제로 관련 메모리 관리 문제를 해결하는지 또는 동일한 문제가 있지만 Xcode 가이 코드로 경고 할만 큼 똑똑하지 않은지 알고 있습니까?
Aaron Brager

이것은 의미 상 같은 것이 아닙니다! performSelector : withObject : AfterDelay :를 사용하면 다음 실행 루프에서 선택기를 수행합니다. 따라서이 메서드는 즉시 반환됩니다.
Florian

10
@Florian 물론 같지 않습니다! 내 대답을 읽으십시오 : 결과가 무효하고 runloop가 순환하기 때문에 수용 가능한 경우 라고 말합니다 . 그게 내 대답 의 첫 번째 문장 입니다.
matt

34

위의 답변을 기반으로 업데이트 된 매크로는 다음과 같습니다. 이것은 리턴 문으로 코드를 랩핑 할 수있게합니다.

#define SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(code)                        \
    _Pragma("clang diagnostic push")                                        \
    _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")     \
    code;                                                                   \
    _Pragma("clang diagnostic pop")                                         \


SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(
    return [_target performSelector:_action withObject:self]
);

6
return매크로 안에있을 필요는 없습니다. return SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING([_target performSelector:_action withObject:self]);또한 작동하고 이상하게 보입니다.
uasi

31

이 코드에는 컴파일러 플래그 또는 직접 런타임 호출이 포함되지 않습니다.

SEL selector = @selector(zeroArgumentMethod);
NSMethodSignature *methodSig = [[self class] instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setSelector:selector];
[invocation setTarget:self];
[invocation invoke];

NSInvocationperformSelector모든 메소드에서 작동하지 않는 것처럼 여러 인수를 설정할 수 있습니다 .


3
이것이 실제로 관련 메모리 관리 문제를 해결하는지 또는 동일한 문제가 있지만 Xcode 가이 코드로 경고 할만 큼 똑똑하지 않은지 알고 있습니까?
Aaron Brager

1
메모리 관리 문제를 해결한다고 말할 수 있습니다. 그러나 기본적으로 동작을 지정할 수 있기 때문입니다. 예를 들어, 호출이 인수를 유지하도록 할 수 있습니다. 내가 아는 한, 현재 수행중인 작업을 알고 있고 잘못된 데이터를 제공하지 않는다는 것을 신뢰함으로써 나타날 수있는 서명 불일치 문제를 해결하려고 시도합니다. 모든 검사를 런타임에 수행 할 수 있는지 확실하지 않습니다. 다른 의견에서 언급했듯이 mikeash.com/pyblog/… 는 불일치가 무엇을 할 수 있는지 잘 설명합니다.
Mihai Timar

20

글쎄, 여기에 많은 답변이 있지만, 약간 다릅니다. 몇 가지 답변을 결합하여 넣었습니다. NSObject 범주를 사용하여 선택기가 void를 반환하는지 확인하고 컴파일러를 억제합니다. 경고.

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "Debug.h" // not given; just an assert

@interface NSObject (Extras)

// Enforce the rule that the selector used must return void.
- (void) performVoidReturnSelector:(SEL)aSelector withObject:(id)object;
- (void) performVoidReturnSelector:(SEL)aSelector;

@end

@implementation NSObject (Extras)

// Apparently the reason the regular performSelect gives a compile time warning is that the system doesn't know the return type. I'm going to (a) make sure that the return type is void, and (b) disable this warning
// See http://stackoverflow.com/questions/7017281/performselector-may-cause-a-leak-because-its-selector-is-unknown

- (void) checkSelector:(SEL)aSelector {
    // See http://stackoverflow.com/questions/14602854/objective-c-is-there-a-way-to-check-a-selector-return-value
    Method m = class_getInstanceMethod([self class], aSelector);
    char type[128];
    method_getReturnType(m, type, sizeof(type));

    NSString *message = [[NSString alloc] initWithFormat:@"NSObject+Extras.performVoidReturnSelector: %@.%@ selector (type: %s)", [self class], NSStringFromSelector(aSelector), type];
    NSLog(@"%@", message);

    if (type[0] != 'v') {
        message = [[NSString alloc] initWithFormat:@"%@ was not void", message];
        [Debug assertTrue:FALSE withMessage:message];
    }
}

- (void) performVoidReturnSelector:(SEL)aSelector withObject:(id)object {
    [self checkSelector:aSelector];

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    // Since the selector (aSelector) is returning void, it doesn't make sense to try to obtain the return result of performSelector. In fact, if we do, it crashes the app.
    [self performSelector: aSelector withObject: object];
#pragma clang diagnostic pop    
}

- (void) performVoidReturnSelector:(SEL)aSelector {
    [self checkSelector:aSelector];

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self performSelector: aSelector];
#pragma clang diagnostic pop
}

@end

'v'를 _C_VOID로 바꿔야합니까? _C_VOID는 <objc / runtime.h>에 선언되어 있습니다.
Rik Renich

16

후손을 위해서, 나는 모자를 반지에 던지기로 결정했다. :)

최근 에는 프로토콜, 블록 등과 같은 것들을 선호 하여 target/ selector패러다임 에서 점점 더 많은 구조 조정이 이루어지고 performSelector있음을 보았습니다.

[NSApp sendAction: NSSelectorFromString(@"someMethod") to: _controller from: nil];

이것들은 깨끗하고 ARC 안전하며 거의 동일한 것으로 대체 performSelector됩니다.objc_msgSend() .

그러나 iOS에서 사용할 수있는 아날로그가 있는지 모르겠습니다.


6
이것을 포함 해 주셔서 감사합니다 [[UIApplication sharedApplication] sendAction: to: from: forEvent:]. iOS에서 사용할 수 있습니다 : . 한 번 살펴 봤지만 도메인 또는 서비스 중간에 UI 관련 클래스를 사용하여 동적 호출을하는 것이 어색한 느낌입니다.
Eduardo Scoz

2
으아! 메소드가 사용 가능한지 점검하고 응답자 체인이 없으면 응답 체인을 걸어야하기 때문에 더 많은 오버 헤드가 발생하고 다른 오류 동작이 있습니다 (응답자 체인을 걷고 아무것도 찾을 수 없으면 NO를 리턴 함) 단순히 충돌하는 대신 메소드에 응답합니다). idfrom-performSelector:...
tc

2
@tc. to:nil이 아닌 한 "응답자 체인을 따라 가지" 않습니다. 미리 검사하지 않고 대상 객체로 바로 이동합니다. 따라서 "더 많은 오버 헤드"가 없습니다. 그것은 훌륭한 해결책은 아니지만 당신이주는 이유가 아닙니다. :)
matt

15

이 스레드에 대한 Matt Galloway의 답변 은 이유를 설명합니다.

다음을 고려하세요:

id anotherObject1 = [someObject performSelector:@selector(copy)];
id anotherObject2 = [someObject performSelector:@selector(giveMeAnotherNonRetainedObject)];

이제 ARC는 첫 번째는 보유 횟수가 1 인 객체를 반환하지만 두 번째는 자동 해제 된 객체를 반환한다는 것을 어떻게 알 수 있습니까?

반환 값을 무시하는 경우 경고를 표시하지 않는 것이 일반적으로 안전합니다. "하지 마십시오"이외의 performSelector에서 유지 된 객체를 실제로 가져와야하는 경우 모범 사례가 무엇인지 잘 모르겠습니다.


14

@ c-road는 여기에 문제 설명이있는 올바른 링크를 제공합니다 . 아래에서 performSelector로 인해 메모리 누수가 발생하는 예를 볼 수 있습니다.

@interface Dummy : NSObject <NSCopying>
@end

@implementation Dummy

- (id)copyWithZone:(NSZone *)zone {
  return [[Dummy alloc] init];
}

- (id)clone {
  return [[Dummy alloc] init];
}

@end

void CopyDummy(Dummy *dummy) {
  __unused Dummy *dummyClone = [dummy copy];
}

void CloneDummy(Dummy *dummy) {
  __unused Dummy *dummyClone = [dummy clone];
}

void CopyDummyWithLeak(Dummy *dummy, SEL copySelector) {
  __unused Dummy *dummyClone = [dummy performSelector:copySelector];
}

void CloneDummyWithoutLeak(Dummy *dummy, SEL cloneSelector) {
  __unused Dummy *dummyClone = [dummy performSelector:cloneSelector];
}

int main(int argc, const char * argv[]) {
  @autoreleasepool {
    Dummy *dummy = [[Dummy alloc] init];
    for (;;) { @autoreleasepool {
      //CopyDummy(dummy);
      //CloneDummy(dummy);
      //CloneDummyWithoutLeak(dummy, @selector(clone));
      CopyDummyWithLeak(dummy, @selector(copy));
      [NSThread sleepForTimeInterval:1];
    }} 
  }
  return 0;
}

내 예제에서 메모리 누수가 발생하는 유일한 방법은 CopyDummyWithLeak입니다. 그 이유는 ARC가 알지 못하고 copySelector가 보유 된 객체를 반환하기 때문입니다.

Memory Leak Tool을 실행하면 다음 그림을 볼 수 있습니다. 여기에 이미지 설명을 입력하십시오 ... 그리고 다른 경우에는 메모리 누수가 없습니다. 여기에 이미지 설명을 입력하십시오


6

Scott Thompson의 매크로를보다 일반적으로 만들려면 :

// String expander
#define MY_STRX(X) #X
#define MY_STR(X) MY_STRX(X)

#define MYSilenceWarning(FLAG, MACRO) \
_Pragma("clang diagnostic push") \
_Pragma(MY_STR(clang diagnostic ignored MY_STR(FLAG))) \
MACRO \
_Pragma("clang diagnostic pop")

그런 다음 다음과 같이 사용하십시오.

MYSilenceWarning(-Warc-performSelector-leaks,
[_target performSelector:_action withObject:self];
                )

FWIW, 나는 매크로를 추가하지 않았습니다. 누군가 내 답변에 추가했습니다. 개인적으로 나는 매크로를 사용하지 않을 것입니다. pragma는 코드에서 특별한 경우를 해결하기 위해 존재하며 pragma는 진행 상황에 대해 매우 명시 적이며 직접적입니다. 나는 그것들을 매크로 뒤에 숨기거나 추상화하는 대신 제자리에 유지하는 것을 선호하지만, 그것은 단지 나입니다. YMMV.
Scott Thompson

@ScottThompson 공정하다. 나를 위해 코드 기반 에서이 매크로를 쉽게 검색 할 수 있으며 일반적으로 기본 문제를 처리하기 위해 침묵하지 않은 경고를 추가합니다.
벤 플린

6

경고를 억제하지 마십시오!

컴파일러로 땜질을하는 12 가지 이상의 대안 솔루션이 있습니다.
최초의 구현 시점에서 영리한 동안 지구상의 엔지니어는 거의 발자국을 따라갈 수 없으며이 코드는 결국 중단됩니다.

안전한 경로 :

이러한 모든 솔루션은 원래 의도와 약간의 차이로 작동합니다. 원한다면 그렇게 param할 수 있다고 가정하십시오 nil.

안전한 경로, 동일한 개념 동작 :

// GREAT
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:YES];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:YES modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:YES];
[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:YES modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

안전한 경로, 약간 다른 동작 :

(참조 응답을)
대신 임의의 스레드를 사용합니다 [NSThread mainThread].

// GOOD
[_controller performSelector:selector withObject:anArgument afterDelay:0];
[_controller performSelector:selector withObject:anArgument afterDelay:0 inModes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelectorInBackground:selector withObject:anArgument];

[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:NO];
[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:NO modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

위험한 경로

어떤 종류의 컴파일러 침묵이 필요합니다. 현 시점에서, 그것은 참고 에서 휴식을 스위프트 .

// AT YOUR OWN RISK
[_controller performSelector:selector];
[_controller performSelector:selector withObject:anArgument];
[_controller performSelector:selector withObject:anArgument withObject:nil];

3
문구가 매우 잘못되었습니다. 안전한 경로는 전혀 위험하지 않습니다. 경고를 암시 적으로 숨기므로 더 위험합니다.
Bryan Chen

나는 말을 모욕하지 않도록 고칠 것이지만, 나는 내 말을지지한다. 침묵 경고를 수용 할 수있는 유일한 경우는 코드를 소유하지 않은 경우입니다. 엔지니어는 모든 결과를 이해하지 않고 무음 코드를 안전하게 유지할 수 없습니다. 이는이 주장을 읽은 것을 의미하며,이 관행은 명백히 위험합니다. 특히 12 개의 일반 영어로 강력한 대안을 고려할 경우.
SwiftArchitect

1
아뇨, 당신은 내 요점을 얻지 못했습니다. 사용하는 performSelectorOnMainThread것은 경고를 끄는 좋은 방법 이 아니며 부작용이 있습니다. (메모리 누수를 해결하지는 않습니다.) 엑스트라 #clang diagnostic ignored 는 매우 분명한 방식으로 경고를 명시 적으로 억제합니다.
Bryan Chen

- (void)메소드 에서 선택기를 수행하는 것이 실제 문제라는 점은 사실입니다.
SwiftArchitect

그리고 이것을 통해 여러 개의 인수가있는 선택자를 어떻게 호출하고 동시에 안전합니까? @SwiftArchitect
CATALIN

4

ARC를 사용하고 있으므로 iOS 4.0 이상을 사용해야합니다. 즉, 블록을 사용할 수 있습니다. 선택기를 기억하는 대신 블록을 대신 사용하는 경우 ARC는 실제로 진행중인 작업을 더 잘 추적 할 수 있으며 실수로 메모리 누수가 발생할 위험을 감수 할 필요가 없습니다.


실제로 블록을 사용하면 실수로 ARC가 해결하지 못하는 유지주기를 매우 쉽게 만들 수 있습니다. 나는 당신 self이 ivar를 통해 암시 적으로 사용했을 때 (예 : ivar대신 self->ivar) 컴파일러 경고가 있었으면 좋겠다 .
tc.

당신은 -Wimplicit-retain-self?
OrangeDog

2

블록 접근 방식을 사용하는 대신 몇 가지 문제가 발생했습니다.

    IMP imp = [_controller methodForSelector:selector];
    void (*func)(id, SEL) = (void *)imp;

다음과 같이 NSInvocation을 사용합니다.

    -(void) sendSelectorToDelegate:(SEL) selector withSender:(UIButton *)button 

    if ([delegate respondsToSelector:selector])
    {
    NSMethodSignature * methodSignature = [[delegate class]
                                    instanceMethodSignatureForSelector:selector];
    NSInvocation * delegateInvocation = [NSInvocation
                                   invocationWithMethodSignature:methodSignature];


    [delegateInvocation setSelector:selector];
    [delegateInvocation setTarget:delegate];

    // remember the first two parameter are cmd and self
    [delegateInvocation setArgument:&button atIndex:2];
    [delegateInvocation invoke];
    }

1

인수를 전달할 필요가 없으면 쉬운 해결 방법을 사용하는 것 valueForKeyPath입니다. 이것은 Class객체 에서도 가능 합니다.

NSString *colorName = @"brightPinkColor";
id uicolor = [UIColor class];
if ([uicolor respondsToSelector:NSSelectorFromString(colorName)]){
    UIColor *brightPink = [uicolor valueForKeyPath:colorName];
    ...
}

-2

여기에서 프로토콜을 사용할 수도 있습니다. 따라서 다음과 같은 프로토콜을 작성하십시오.

@protocol MyProtocol
-(void)doSomethingWithObject:(id)object;
@end

선택자에게 전화해야하는 수업에는 @property가 있습니다.

@interface MyObject
    @property (strong) id<MyProtocol> source;
@end

@selector(doSomethingWithObject:)MyObject 인스턴스 를 호출해야하는 경우 다음을 수행하십시오.

[self.source doSomethingWithObject:object];

2
고마워, NSSelectorFromString 사용의 요점은 런타임 중에 어떤 셀렉터를 호출할지 모르는 경우입니다.
Eduardo Scoz
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.