1290

ARCコンパイラから次の警告が表示されます。

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

これが私がしていることです:

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

なぜこの警告が表示されるのですか?コンパイラがセレクターが存在するかどうかをチェックできないことは理解していますが、なぜそれがリークを引き起こすのでしょうか?そして、この警告が表示されないようにコードを変更するにはどうすればよいですか?

4

19 に答える 19

1235

解決

コンパイラは、これについて警告しています。この警告を単に無視する必要があることは非常にまれであり、回避するのは簡単です。方法は次のとおりです。

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がに応答しますが、Objective-CランタイムでmethodForSelector:使用することもできclass_getMethodImplementationます(のようなプロトコル参照しかない場合に便利ですid<SomeProto>)。これらの関数ポインタはIMPsと呼ばれ、単純なtypedefed関数ポインタ(id (*IMP)(id, SEL, ...)1です。これは、メソッドの実際のメソッドシグネチャに近い場合がありますが、常に正確に一致するとは限りません。

を取得したらIMP、ARCが必要とするすべての詳細(2つの暗黙の非表示引数self_cmdすべてのObjective-Cメソッド呼び出しを含む)を含む関数ポインターにキャストする必要があります。これは3行目で処理されます((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では、ランタイムが呼び出しているメソッドの結果をどう処理するかを知る必要があるためです。結果は次のようになります:、、、、、など。ARCは通常、操作しているオブジェクトタイプのヘッダーからこの情報を取得しvoidます。3intcharNSString *id

ARCが戻り値として考慮するのは実際には4つだけです。4

  1. 非オブジェクトタイプ(、、voidなどint)を無視する
  2. オブジェクト値を保持し、使用されなくなったら解放します(標準の仮定)
  3. 使用されなくなったときに新しいオブジェクト値を解放します(init/copyファミリーのメソッドまたは属性付きのメソッドns_returns_retained
  4. 何もせず、返されたオブジェクト値がローカルスコープで有効であると想定します(最も内側のリリースプールがドレインされるまで、に起因しますns_returns_autoreleased

tomethodForSelector:の呼び出しは、呼び出しているメソッドの戻り値がオブジェクトであると想定しますが、それを保持/解放しません。したがって、上記の#3のようにオブジェクトが解放されることになっている場合(つまり、呼び出しているメソッドが新しいオブジェクトを返す場合)、リークが発生する可能性があります。

そのreturnvoidまたは他の非オブジェクトを呼び出そうとしているセレクターの場合、コンパイラー機能が警告を無視できるようにすることができますが、それは危険な場合があります。Clangが、ローカル変数に割り当てられていない戻り値を処理する方法を数回繰り返しているのを見てきました。methodForSelector:ARCを有効にすると、使用したくない場合でも、返されたオブジェクト値を保持および解放できない理由はありません。コンパイラの観点からは、それは結局のところオブジェクトです。つまり、呼び出しているメソッドがsomeMethod非オブジェクト(を含むvoid)を返している場合、ガベージポインタ値が保持/解放されてクラッシュする可能性があります。

追加の引数

1つの考慮事項は、これはで発生するのと同じ警告でperformSelector:withObject:あり、そのメソッドがパラメーターをどのように消費するかを宣言しないと、同様の問題が発生する可能性があることです。ARCでは、消費されたパラメーターを宣言できます。メソッドがパラメーターを消費した場合、最終的にはゾンビにメッセージを送信してクラッシュします。ブリッジキャストでこれを回避する方法はいくつかありますが、実際には、IMP上記の関数ポインタの方法論を使用する方がよいでしょう。消費されたパラメータが問題になることはめったにないので、これが発生する可能性は低いです。

静的セレクター

興味深いことに、コンパイラーは静的に宣言されたセレクターについて文句を言いません。

[_controller performSelector:@selector(someMethod)];

これは、コンパイラーがコンパイル中にセレクターとオブジェクトに関するすべての情報を実際に記録できるためです。何かについて何も仮定する必要はありません。(私は1年前にソースを見てこれをチェックしましたが、現在は参照がありません。)

抑制

この警告の抑制が必要であり、優れたコード設計が必要となる状況を考えようとすると、私は空白になります。この警告を消音する必要がある(そして上記は物事を適切に処理しない)経験があった場合は、誰かが共有してください。

もっと

これを処理するためにを構築することも可能NSMethodInvocationですが、そうすることはより多くのタイピングを必要とし、また遅いので、それを行う理由はほとんどありません。

歴史

performSelector:メソッドファミリーがObjective-Cに最初に追加されたとき、ARCは存在しませんでした。ARCの作成中に、Appleは、名前付きセレクタを介して任意のメッセージを送信するときにメモリの処理方法を明示的に定義する他の手段を使用するように開発者をガイドする方法として、これらのメソッドに対して警告を生成する必要があると判断しました。Objective-Cでは、開発者は生の関数ポインターでCスタイルのキャストを使用してこれを行うことができます。

Swiftの導入により、Appleは一連のメソッドを「本質的に安全ではない」と文書化し、 Swiftでは使用できなくなりました。performSelector:

時間の経過とともに、この進行が見られました。

  1. Objective-Cの初期のバージョンでは、performSelector:(手動メモリ管理)が可能です
  2. ARCを使用したObjective-Cは、performSelector:
  3. Swiftは、これらのメソッドにアクセスできずperformSelector:、「本質的に安全ではない」と文書化しています。

ただし、名前付きセレクターに基づいてメッセージを送信するという考え方は、「本質的に安全でない」機能ではありません。このアイデアは、Objective-Cや他の多くのプログラミング言語で長い間成功裏に使用されてきました。


1すべてのObjective-Cメソッドには2つの隠し引数がselfあり_cmd、メソッドを呼び出すと暗黙的に追加されます。

2 Cでは、関数の呼び出しNULLは安全ではありません。コントローラーの存在を確認するために使用されるガードは、オブジェクトがあることを確認します。IMPしたがって、から取得することはわかっていますmethodForSelector:(ただし_objc_msgForward、メッセージ転送システムへのエントリである可能性があります)。基本的に、ガードが配置されていれば、呼び出す関数があることがわかります。

3実際には、オブジェクトをとして宣言idし、すべてのヘッダーをインポートしていない場合、間違った情報を取得する可能性があります。コンパイラが問題ないと判断したコードがクラッシュする可能性があります。これは非常にまれですが、発生する可能性があります。通常、2つのメソッドシグネチャのどちらから選択するかがわからないという警告が表示されます。

4詳細については、保持された戻り値と保持されていない戻り値に関するARCリファレンスを参照してください。

于 2013-11-18T21:44:29.753 に答える
1187

Xcode4.2のLLVM3.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]
);
于 2011-10-28T19:30:28.793 に答える
208

これについての私の推測はこれです:セレクターはコンパイラーに知られていないので、ARCは適切なメモリー管理を強制することができません。

実際、メモリ管理が特定の規則によってメソッドの名前に関連付けられている場合があります。具体的には、便利なコンストラクターmakeメソッドを考えています。前者は慣例により自動解放されたオブジェクトを返します。後者は保持されたオブジェクトです。規則はセレクターの名前に基づいているため、コンパイラーがセレクターを認識していない場合、適切なメモリー管理ルールを適用できません。

これが正しければ、メモリ管理に関してすべてが問題ないことを確認すれば、コードを安全に使用できると思います(たとえば、メソッドが割り当てたオブジェクトを返さないようにします)。

于 2011-08-10T20:43:48.377 に答える
121

プロジェクトのビルド設定で、[その他の警告フラグ( )]の下に次のWARNING_CFLAGSように追加します。
-Wno-arc-performSelector-leaks

ここで、呼び出しているセレクターによってオブジェクトが保持またはコピーされないことを確認してください。

于 2011-10-31T13:57:19.760 に答える
112

コンパイラが警告のオーバーライドを許可するまでの回避策として、ランタイムを使用できます。

ヘッダーが必要です:

#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")];
于 2011-08-16T04:56:52.443 に答える
88

実行セレクターのあるファイルでのみエラーを無視するには、次のように#pragmaを追加します。

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

これにより、この行の警告は無視されますが、プロジェクトの残りの部分では引き続き許可されます。

于 2012-01-18T21:31:49.770 に答える
70

奇妙ですが真実:許容できる場合(つまり、結果が無効で、runloopを1回循環させてもかまわない場合)、これがゼロであっても、遅延を追加します。

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

これにより、警告が削除されます。これは、おそらく、オブジェクトを返すことができず、何らかの理由で誤って管理されていることをコンパイラーに安心させるためです。

于 2012-11-11T19:19:30.383 に答える
34

上記の回答に基づいて更新されたマクロを次に示します。これにより、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]
);
于 2013-05-07T14:58:50.417 に答える
31

このコードには、コンパイラフラグや直接のランタイム呼び出しは含まれていません。

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、どのメソッドでも機能します。

于 2012-02-01T15:46:50.740 に答える
20

ええと、ここにはたくさんの答えがありますが、これは少し違うので、私が入れようと思ったいくつかの答えを組み合わせます。セレクターがvoidを返すことを確認し、コンパイラーを抑制する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://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
于 2014-02-21T04:32:10.017 に答える
16

後世のために、私は帽子をリングに投げ込むことにしました:)

最近、プロトコルやブロックなどを優先して、 target/パラダイムから離れて再構築が進んでいます。ただし、これまでに数回使用したドロップインの代替品が1つあります。selectorperformSelector

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

これらは、クリーンでARCセーフであり、とほとんど同じであるように見えperformSelectorますobjc_msgSend()

ただし、iOSで利用できるアナログがあるかどうかはわかりません。

于 2012-02-26T04:17:19.163 に答える
15

このスレッドに関するMattGallowayの回答は、その理由を説明しています。

次のことを考慮してください。

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

さて、ARCは、最初のオブジェクトが保持カウント1のオブジェクトを返し、2番目のオブジェクトが自動解放されたオブジェクトを返すことをどのように知ることができますか?

戻り値を無視する場合は、一般的に警告を抑制しても安全だと思われます。「実行しない」以外に、performSelectorから保持されたオブジェクトを取得する必要がある場合のベストプラクティスはわかりません。

于 2012-05-15T20:17:43.390 に答える
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が保持されたオブジェクトを返すためです。

メモリリークツールを実行すると、次の図が表示 されここに画像の説明を入力してください ます。...その他の場合はメモリリークはありません。 ここに画像の説明を入力してください

于 2012-12-25T10:23:42.233 に答える
6

スコット・トンプソンのマクロをより一般的にするには:

// 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];
                )
于 2015-08-03T21:46:12.403 に答える
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]];

危険なルート

ある種のコンパイラサイレンシングが必要ですが、これは必ず壊れます。現時点では、 Swiftで壊れたことに注意してください。

// AT YOUR OWN RISK
[_controller performSelector:selector];
[_controller performSelector:selector withObject:anArgument];
[_controller performSelector:selector withObject:anArgument withObject:nil];
于 2015-12-31T03:58:37.297 に答える
4

ARCを使用しているため、iOS4.0以降を使用している必要があります。これは、ブロックを使用できることを意味します。セレクターを覚えて実行する代わりにブロックを取得した場合、ARCは実際に何が起こっているかをより正確に追跡できるため、誤ってメモリリークが発生するリスクを冒す必要はありません。

于 2011-11-23T02:33:40.493 に答える
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];
    }
于 2015-05-21T08:52:00.310 に答える
2

引数を渡す必要がない場合、簡単な回避策はを使用することvalueForKeyPathです。これはClassオブジェクトでも可能です。

NSString *colorName = @"brightPinkColor";
id uicolor = [UIColor class];
if ([uicolor respondsToSelector:NSSelectorFromString(colorName)]){
    UIColor *brightPink = [uicolor valueForKeyPath:colorName];
    ...
}
于 2017-02-15T04:08:51.943 に答える
-1

ここでプロトコルを使用することもできます。したがって、次のようなプロトコルを作成します。

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

セレクターを呼び出す必要があるクラスには、@propertyがあります。

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

MyObjectのインスタンスを呼び出す必要が@selector(doSomethingWithObject:)ある場合は、次のようにします。

[self.source doSomethingWithObject:object];
于 2013-08-03T11:34:34.490 に答える