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
★★★★★★★★★★★★★★★★★」_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는 일반적으로 작업 중인 3개체 유형의 헤더에서 이 정보를 가져옵니다.
ARC가 고려할 수 있는 리턴 4값은 다음 4가지뿐입니다.
- 타입은 무시합니다( 「」 「」
void
,int
의 개요) - 오브젝트 값을 유지했다가 사용하지 않게 되면 해방한다(표준 전제 조건)
- 않게 되면 합니다(「」의 ).
init
copy
또는 "Family"로 됩니다.ns_returns_retained
) - 하지 까지).
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에서는 사용할 수 없는 여러 가지 방법을 제공합니다.
시간이 지남에 따라 다음과 같은 진전이 있었습니다.
- 의 초기 에서는 Objective-C가 됩니다.
performSelector:
메모리 수동 메모리 관리) - 는 ARC 합니다.
performSelector:
- 는 Swift에 할 수 .
performSelector:
이 안전하지 않다고 .
단, 이름 있는 실렉터에 근거해 메시지를 송신하는 생각은, 「원래 안전하지 않은」기능은 아닙니다.이 아이디어는 Objective-C뿐만 아니라 다른 많은 프로그래밍 언어에서도 오랫동안 성공적으로 사용되어 왔습니다.
1 모든 Objective-C 메서드에는 2개의 숨겨진 인수가 있습니다.self
★★★★★★★★★★★★★★★★★」_cmd
메서드를 호출할 때 암묵적으로 추가됩니다.
2 호출하다NULL
C에서는 기능이 안전하지 않습니다.컨트롤러의 존재를 확인하기 위해 사용되는 가드는 오브젝트가 있음을 확인합니다. 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
'programing' 카테고리의 다른 글
WPF 버튼의 디폴트 마우스 오버 효과를 삭제하려면 어떻게 해야 합니까? (0) | 2023.04.12 |
---|---|
어떻게 하면 한 사용자의 커밋만 git 로그를 볼 수 있나요? (0) | 2023.04.12 |
Bash의 'if' 문에서 두 문자열 변수를 비교하려면 어떻게 해야 합니까? (0) | 2023.04.12 |
cociapods - '팟 설치'에 시간이 오래 걸린다. (0) | 2023.04.12 |
SQL 서버에서 NULL = NULL이 false로 평가되는 이유 (0) | 2023.04.07 |