Wednesday, May 30, 2012

performSelector may cause a leak because its selector is unknown

I'm getting the following warning by the ARC compiler:

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

Here's what I'm doing:

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

Why do I get this warning? I understand the compiler can't check if the selector exists or not, but why would that cause a leak? And how can I change my code so that I don't get this warning anymore?

Source: Tips4all


  1. My guess about this is this: since the selector is unknown to the compiler, ARC cannot enforce proper memory management.

    In fact, there are times when memory management is tied to the name of the method by a specific convention. Specifically, I am thinking of convenience constructors versus make methods; the former return by convention an autoreleased object; the latter a retained object. The convention is based on the names of the selector, so if the compiler does not know the selector, then it cannot enforce the proper memory management rule.

    If this is correct, I think that you can safely use your code, provided you make sure that everything is ok as to memory management (e.g., that your methods do not return objects that they allocate).

  2. In the LLVM 3.0 compiler in Xcode 4.2 you can suppress the warning as follows:

    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self.ticketTarget performSelector: self.ticketAction withObject: self];
    #pragma clang diagnostic pop

  3. As a workaround until the compiler allows overriding the warning, you can use the runtime

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

    instead of

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

    You'll have to #import <objc/message.h>

  4. In your project Build Settings, under Other Warning Flags (WARNING_CFLAGS), add

    Now just make sure that the selector you are calling does not cause your object to be retained or copied.

  5. To ignore the error only in the file with the perform selector, add a #pragma as follows:

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

    This would ignore the warning on this line, but still allow it throughout the rest of your project.

  6. This code doesn't involve compiler flags or direct runtime calls:

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

    NSInvocation allows multiple arguments to be set so unlike performSelector this will work on any method.

  7. For posterity's sake, I've decided to throw my hat into the ring :)

    Recently I've been seeing more and more restructuring away from the target/selector paradigm, in favor of things such as protocols, blocks, etc. However, there is one drop-in replacement for performSelector that I've used a few times now:

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

    These seem to be a clean, ARC-safe, and nearly identical replacement for performSelector without having to much about with objc_msgSend().

    Though, I have no idea if there is an analog available on iOS.

  8. Because you are using ARC you must be using iOS 4.0 or later. This means you could use blocks. If instead of remembering the selector to perform you instead took a block, ARC would be able to better track what is actually going on and you wouldn't have to run the risk of accidentally introducing a memory leak.

  9. Matt Galloway's answer on this thread explains the why:

    Consider the following:

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

    Now, how can ARC know that the first returns an object with a retain count of 1 but the second
    returns an object which is autoreleased?

    It seems that it is generally safe to suppress the warning if you are ignoring the return value. I'm not sure what the best practice is if you really need to get a retained object from performSelector -- other than "don't do that".

  10. Try this:

    __unsafe_unretained SEL mySelector = NSSelectorFromString(@"someMethod");

    [_controller performSelector:mySelector];

    The idea is there are some flags to help ARC understand how memory is managed for some trickier cases. If that doesn't remove the warning some other flag might.