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

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

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

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

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

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



답변

해결책

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

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 참조 를 참조하십시오.


답변

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]
);


답변

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

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

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


답변

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

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


답변

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

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

대신에

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

당신은해야합니다

#import <objc/message.h>


답변

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

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

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


답변

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

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

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