ios - performselector may cause a leak because its selector is unknown




selector가 알 수 없기 때문에 performSelector에서 누수가 발생할 수 있습니다. (13)

ARC 컴파일러에 의해 다음과 같은 경고가 표시됩니다.

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

여기 내가하고있는 일이있다.

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

왜 내가이 경고를 받는가? 나는 컴파일러가 셀렉터가 존재하는지 여부를 확인할 수 없다는 것을 이해한다. 그러나 왜 그것이 누출을 야기 할까? 그리고 어떻게하면이 경고 메시지가 나타나지 않도록 코드를 변경할 수 있습니까?


경고를 표시하지 마십시오!

컴파일러를 고치기 위해 12 가지 이상의 대안 솔루션이 있습니다.
처음 구현 한 순간에 당신이 영리한 동안, 지구상의 엔지니어가 당신의 발자취를 따라갈 수는 없으며, 결국이 코드는 깨질 것입니다.

안전한 경로 :

이러한 모든 솔루션이 원래의 의도와 어느 정도 유사하게 작동합니다. 원하는 경우 paramnil 설정할 수 있다고 가정합니다.

안전한 경로, 동일한 개념적 행동 :

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

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

( this 응답을보십시오)
[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]];

위험한 경로

컴파일러가 멈추는 일종의 컴파일러가 필요합니다. 현재는 Swift 에서 깨졌습니다.

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

해결책

컴파일러는 이유에 대해 경고합니다. 이 경고를 무시해야한다는 것은 매우 드뭅니다. 쉽게 해결할 수 있습니다. 방법은 다음과 같습니다.

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 함수 포인터를 컨트롤러에 요청하고 있습니다. 모든 NSObjectclass_getMethodImplementation methodForSelector: 응답하지만 Objective-C 런타임에서 class_getMethodImplementation 을 사용할 수도 있습니다 ( id<SomeProto> 와 같은 프로토콜 참조 만있는 경우 유용). 이 함수 포인터는 IMP 라고하며 단순한 typedef 함수 포인터 ( id (*IMP)(id, SEL, ...) )입니다. 이것은 메소드의 실제 메소드 서명과 유사 할 수 있지만 항상 정확하게 일치하는 것은 아닙니다.

IMP _cmd ARC가 필요로하는 모든 세부 정보 (모든 Objective-C 메서드 호출의 self 암시 적 인수 self_cmd 포함)를 포함하는 함수 포인터로 캐스팅해야합니다. 이것은 세 번째 행에서 처리됩니다. 오른쪽에있는 (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 가지뿐입니다.

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

methodForSelector: 에 대한 호출은 호출하는 메소드의 반환 값이 객체이지만이를 유지하거나 해제하지 않는다고 가정합니다. 그래서 위의 # 3에서와 같이 객체가 릴리즈 될 것으로 예상되는 경우 (즉, 호출하는 메소드가 새 객체를 반환하는 경우) 누수가 발생할 수 있습니다.

void 또는 기타 비 객체를 호출하려고하는 선택기의 void 컴파일러 기능을 사용하여 경고를 무시할 수 있지만 위험 할 수 있습니다. Clang이 로컬 변수에 할당되지 않은 반환 값을 처리하는 방법을 몇 번 반복 해 보았습니다. ARC를 사용하면 methodForSelector: 에서 반환 된 객체 값을 유지하고 해제 할 수 없다는 이유는 없습니다. 사용하지 않으려는 경우에도 마찬가지입니다. 컴파일러의 관점에서, 그것은 결국 객체입니다. 즉, someMethod 메서드가 비 객체 ( void 포함)를 반환하면 garbage pointer 값이 유지 / 해제되고 충돌 할 수 있습니다.

추가 인수

한 가지 고려 사항은 performSelector:withObject: 와 동일한 경고가 발생하며 해당 메서드가 매개 변수를 사용하는 방식을 선언하지 않고 비슷한 문제가 발생할 수 있다는 것입니다. ARC는 소비 된 매개 변수 를 선언 할 수있게 해주 며, 매개 변수를 소비하면 결국 좀비에게 메시지를 보내고 충돌합니다. 브릿지 캐스트를 사용하여이 문제를 해결할 수있는 방법이 있지만 위의 IMP 및 함수 포인터 방법을 사용하는 것이 좋습니다. 소비 된 매개 변수는 거의 문제가되지 않기 때문에 이것이 올 수는 없습니다.

정적 선택기

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

[_controller performSelector:@selector(someMethod)];

그 이유는 컴파일러가 실제로 컴파일하는 동안 선택기와 객체에 대한 모든 정보를 기록 할 수 있기 때문입니다. 어떤 것에 대해서도 가정 할 필요가 없습니다. (나는 소스를보고 너무 일찍 이것을 확인했지만 현재는 참조가 없다.)

억압

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

이것도 처리 할 수있는 NSMethodInvocation 을 구축하는 것이 가능하지만, 그렇게하기 위해서는 타이핑이 많이 필요하고 또한 느려지므로 그럴 필요가 거의 없습니다.

역사

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

Swift가 소개됨에 따라 Apple performSelector: 의 메소드를 "본질적으로 안전하지 않은"것으로 문서화 했으며 Swift는 사용할 수 없습니다.

시간이 지남에 따라, 우리는이 진전을 보았습니다 :

  1. Objective-C의 초기 버전에서는 performSelector: 수동 메모리 관리)
  2. ARC가있는 Objective-C는 performSelector: 사용에 대해 경고합니다 performSelector:
  3. Swift는 performSelector: 액세스 할 수 없으며 이러한 메서드를 "본질적으로 안전하지 않은"것으로 문서화합니다.

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

1 모든 Objective-C 메소드에는 메소드 호출시 암시 적으로 추가되는 두 개의 숨겨진 인수 self_cmd 가 있습니다.

2 NULL 함수를 호출하는 것은 C에서 안전하지 않습니다. 컨트롤러가 있는지 확인하는 데 사용되는 가드는 객체가 있는지 확인합니다. 그러므로 우리는 _objc_msgForward methodForSelector: 에서 IMP 를 얻는다는 것을 알고있다 methodForSelector: (메시지 전달 시스템에 들어가는 _objc_msgForward ). 기본적으로 경비원은 전화를 걸 수있는 기능이 있음을 알고 있습니다.

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

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


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


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

perform selector가있는 파일에서만 오류를 무시하려면 다음과 같이 #pragma를 추가하십시오.

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

이렇게하면이 줄의 경고는 무시되지만 프로젝트의 나머지 부분에서도이 경고를 허용합니다.


글쎄, 여기에 많은 답변이 있지만, 약간의 차이가 있기 때문에 몇개의 답을 조합해서 입력 해 보았습니다. 셀렉터가 무효를 리턴하는지 확인하기 위해 NSObject 카테고리를 사용하고 있습니다. 또한 컴파일러를 숨 깁니다. 경고.

#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://.com/questions/7017281/performselector-may-cause-a-leak-because-its-selector-is-unknown

- (void) checkSelector:(SEL)aSelector {
    // See http://.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

위에서 주어진 대답을 기반으로 업데이트 된 매크로가 있습니다. 이것은 return 문을 사용하여 코드를 래핑 할 수 있어야합니다.

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

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

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

NSInvocation 은 여러 인수를 설정할 수 있으므로 performSelector 와 달리 모든 메서드에서 작동합니다.


컴파일러에서 경고를 무시할 수있을 때까지 해결 방법으로 런타임을 사용할 수 있습니다

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

대신에

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

너는해야 할 것이다.

#import <objc/message.h>


프로젝트 설정 에서 기타 경고 플래그 ( WARNING_CFLAGS ) 아래의 설정 추가
-Wno-arc-performSelector-leaks

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


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

다음을 고려하세요:

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

이제 ARC는 첫 번째 객체가 보유 개수가 1 인 객체를 반환하지만 두 번째 객체가 자동으로 리 릴리스되는 객체를 반환한다는 것을 어떻게 알 수 있습니까?

반환 값을 무시하는 경우 경고를 표시하지 않는 것이 일반적으로 안전합니다. performSelector에서 유지 된 객체를 가져와야 할 경우 가장 좋은 방법이 무엇인지 모르겠다 - "하지 마라".


나에게 몇 가지 문제점을 준 블록 접근법을 사용하는 대신 :

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

인수를 전달할 필요가 없다면 쉽게 해결할 수 있습니다 valueForKeyPath. 이것은 심지어 Class객체에서 가능 합니다.

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




automatic-ref-counting