programing

performSelector의 셀렉터를 알 수 없기 때문에 누수가 발생할 수 있습니다.

codeshow 2023. 4. 12. 22:40
반응형

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_getMethodImplementation에서 (Objective-C와 같은 참조만 합니다.)id<SomeProto>를 '지식 포인터'라고 IMP입니다.typedef 포인터ed "d")id (*IMP)(id, SEL, ...)이는 메서드의 실제 메서드시그니처와 비슷할 수 있지만 항상 정확하게 일치하는 것은 아닙니다.1

그 다음에IMP로 하는 모든 인 숨김 인수 포함)을하는 함수 포인터에 self ★★★★★★★★★★★★★★★★★」_cmdObjective-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 *,idARC는 일반적으로 작업 중인 3개체 유형의 헤더에서 이 정보를 가져옵니다.

ARC가 고려할 수 있는 리턴 4값은 다음 4가지뿐입니다.

  1. 타입은 무시합니다( 「」 「」void,int 의 개요)
  2. 오브젝트 값을 유지했다가 사용하지 않게 되면 해방한다(표준 전제 조건)
  3. 않게 되면 합니다(「」의 ).initcopy 또는 "Family"로 됩니다.ns_returns_retained)
  4. 하지 까지).ns_returns_autoreleased)

의 콜methodForSelector:는 호출한 메서드의 반환값이 오브젝트라고 가정하지만 유지/해제는 하지 않습니다.따라서 위의 #3에서와 같이 오브젝트가 해제되어야 하는 경우(즉, 호출하는 메서드가 새 오브젝트를 반환함) 누출이 발생할 수 있습니다.

void또는 컴파일러 기능이 경고를 무시하도록 설정할 수 있지만 위험할 수 있습니다.Clang이 지역 변수에 할당되지 않은 반환 값을 처리하는 방법을 몇 번 반복하는 것을 보았습니다.된 객체 및 할 수 methodForSelector:쓰고 싶지 않아도요.컴파일러의 관점에서 보면 그것은 결국 객체이다. 메서드가, 호출하고 있는 메서드의 는,someMethod비객체 포함)를void되어 크래시 될 수 의 경우 가비지 포인터 값이 유지/해방되어 크래시가 발생할 수 있습니다.

추가 인수

가지 은 이 한다는 것입니다.performSelector:withObject:이 메서드가 파라미터를 어떻게 소비하는지 선언하지 않으면 유사한 문제가 발생할 수 있습니다.ARC에서는 사용된 파라미터를 선언할 수 있습니다.메서드가 파라미터를 소비하면 결국 좀비에게 메시지가 전송되어 크래시가 발생합니다.브리지드 캐스팅을 사용하여 이 문제를 해결할 수 있는 방법이 있습니다. 그러나 실제로는 단순히 이 방법을 사용하는 것이 더 나을 것입니다.IMP및 위의 함수 포인터 방법론을 참조하십시오.사용되는 파라미터는 거의 문제가 되지 않기 때문에 이 문제는 발생하지 않습니다.

스태틱 셀렉터

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

[_controller performSelector:@selector(someMethod)];

그 이유는 컴파일러가 컴파일 중에 실렉터와 오브젝트에 관한 모든 정보를 기록할 수 있기 때문입니다.아무것도 추측할 필요가 없습니다.(출처를 보고 1년 전에 확인했는데, 지금은 참조가 없습니다.)

억제

이 경고의 억제가 필요하고 좋은 코드 설계가 필요한 상황을 생각하려고 하는데, 아무것도 떠오르지 않습니다.이 경고를 잠재울 필요가 있는 경험(위 내용으로는 제대로 처리되지 않는 경우)이 있는 경우 공유해 주십시오.

NSMethodInvocation이 문제를 해결하려면 더 많은 타이핑이 필요하고 속도가 느리기 때문에 이 작업을 수행할 이유가 거의 없습니다.

역사

?performSelector:오브젝티브-C의 ARC입니다.ARC를 만들면서 애플은 개발자들이 이름 있는 셀렉터를 통해 임의의 메시지를 보낼 때 메모리를 처리하는 방법을 명시적으로 정의하기 위해 다른 방법을 사용하도록 유도하는 방법으로 이러한 방법에 대한 경고를 생성해야 한다고 결정했습니다.Objective-C에서 개발자는 원시 함수 포인터에 C 스타일 캐스트를 사용하여 이를 수행할 수 있습니다.

Swift의 도입으로 애플은 다음 사항을 문서화했습니다.performSelector: 안전하지 않다며 Swift에서 수 없는 입니다.원래 안전하지 않다"며 Swift에서는 사용할 수 없는 여러 가지 방법을 제공합니다.

시간이 지남에 따라 다음과 같은 진전이 있었습니다.

  1. 의 초기 에서는 Objective-C가 됩니다.performSelector: 메모리 수동 메모리 관리)
  2. 는 ARC 합니다.performSelector:
  3. 는 Swift에 할 수 .performSelector: 이 안전하지 않다고 .

단, 이름 있는 실렉터에 근거해 메시지를 송신하는 생각은, 「원래 안전하지 않은」기능은 아닙니다.이 아이디어는 Objective-C뿐만 아니라 다른 많은 프로그래밍 언어에서도 오랫동안 성공적으로 사용되어 왔습니다.


1 모든 Objective-C 메서드에는 2개의 숨겨진 인수가 있습니다.self ★★★★★★★★★★★★★★★★★」_cmd메서드를 호출할 때 암묵적으로 추가됩니다.

2 호출하다NULLC에서는 기능이 안전하지 않습니다.컨트롤러의 존재를 확인하기 위해 사용되는 가드는 오브젝트가 있음을 확인합니다. We therefore know we'll get an 그러므로 우리는 우리가 얻을 것을 알고 있습니다.IMP부에서methodForSelector: (though it may be (그럴지도 모르지만)_objc_msgForward, , entry into the message forwarding system)., 메시지 전송 시스템에 입력합니다).기본적으로 경비원이 함께 전화하기 위한 기능이 있다는 것을 알고 있습니다.기본적으로, 경비원이 자리를 잡으면 호출할 기능이 있다는 것을 알 수 있습니다.

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 매크로 시스템을 사용하여 플러그마를 숨기려는 경우 매크로를 정의하여 경고를 더 쉽게 억제할 수 있습니다.

#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는 적절한 메모리 관리를 강제할 수 없습니다.

실제로 메모리 관리는 특정 규칙에 따라 메서드 이름과 관련될 수 있습니다.구체적으로는 편리성 컨스트럭터 메이크 방식, 즉 관례에 의한 전자의 반환은 자동 리리스 오브젝트, 후자는 보유 오브젝트라고 생각하고 있습니다.이 규칙은 셀렉터의 이름을 기반으로 하기 때문에 컴파일러가 셀렉터를 인식하지 못하면 적절한 메모리 관리 규칙을 적용할 수 없습니다.

이것이 맞다면 메모리 관리에 문제가 없는지(예를 들어 메서드가 할당한 개체를 반환하지 않는 경우) 확인한다면 코드를 안전하게 사용할 수 있다고 생각합니다.

프로젝트 빌드 설정의 [기타 경고 플래그(Other Warning Flags)](WARNING_CFLAGS 추가, 추가
-Wno-arc-performSelector-leaks

호출하고 있는 셀렉터가 오브젝트를 유지 또는 복사하지 않는지 확인합니다.

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

머리글 필요:

#import <objc/message.h>

그 후, 이하를 시험해 주세요.

// For strict compilers.
((id(*)(id,SEL))objc_msgSend)(_controller, sel_getUid("someMethod"));

또는

// Old answer's code:
objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));

대신:

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

perform selector가 있는 파일 내의 오류만 무시하려면 다음과 같이 #pragma를 추가합니다.

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

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

이상하지만 사실: 허용 가능한 경우(즉, 결과가 무효이고 런루프 사이클을 한 번 허용해도 상관 없음), 이것이 0인 경우에도 지연을 추가합니다.

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

이 경고는 컴파일러가 오브젝트를 반환할 수 없고 어떤 식으로든 잘못 관리할 수 없다는 것을 보증하기 때문에 제거됩니다.

위의 답변을 바탕으로 업데이트된 매크로입니다.이 경우 반품 명세서도 코드를 랩할 수 있습니다.

#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이 방법은 어떤 방법으로든 사용할 수 있습니다.

음, 여기엔 많은 답이 있지만, 이건 좀 다르니까, 몇 가지 답을 조합해서 넣어야겠다고 생각했어요.NSObject 카테고리를 사용하고 있습니다.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

후세를 위해 출사표를 던지기로 결심했습니다.

들어 구조 더 target/selector프로토콜, 블록 등과 같은 것을 선호하는 패러다임단, 1개의 드롭인 대체품이 있습니다.performSelector인가 사용한 것 같습니다.

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

하며 ARC를 거의 한 것으로 .performSelector 볼일 없이objc_msgSend().

다만, iOS에 아날로그가 있는지 어떤지는 모르겠습니다.

Matt Galloway의 답변은 그 이유를 설명합니다.

다음 사항을 고려하십시오.

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

첫 번째 ARC는 보유 카운트가 1인 객체를 반환하고 두 번째 ARC는 자동 리뉴얼된 객체를 반환하는 것을 어떻게 알 수 있을까요?

일반적으로 반환값을 무시하는 경우에는 경고를 억제하는 것이 안전하다고 생각됩니다.perform Selector에서 보유 개체를 정말로 가져와야 하는 경우 베스트 프랙티스가 무엇인지 잘 모르겠습니다('Don't that' 이외).

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

이 예에서 메모리 누수를 일으키는 유일한 방법은 Copy Dummy With Leak입니다.그 이유는 ARC가 알 수 없기 때문에 copySelector가 보유 객체를 반환하기 때문입니다.

Leak 을 볼 수 .여기에 이미지 설명 입력하지 않습니다.하다여기에 이미지 설명 입력

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

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

컴파일러를 만지작거릴 수 있는 대체 솔루션이 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]];

위험한 루트

컴파일러의 음소거가 필요한데, 깨지기 마련이죠.현시점에서는 Swift에서 파손이 발생하고 있는 것에 주의해 주십시오.

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

ARC를 사용 중이므로 iOS 4.0 이후를 사용해야 합니다.즉, 블록을 사용할 수 있습니다.셀렉터를 기억해 두는 대신 블록을 하면 ARC는 실제로 무슨 일이 일어나고 있는지 더 잘 추적할 수 있기 때문에 실수로 메모리 누수가 발생할 위험을 감수할 필요가 없습니다.

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

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

여기 프로토콜을 사용할 수도 있습니다.다음과 같은 프로토콜을 만듭니다.

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

셀렉터를 호출해야 하는 클래스에는 @property가 있습니다.

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

@selector(doSomethingWithObject:)MyObject my my음 음음 음음 。

[self.source doSomethingWithObject:object];

언급URL : https://stackoverflow.com/questions/7017281/performselector-may-cause-a-leak-because-its-selector-is-unknown

반응형