performSelector may cause a leak because its selec

2018-12-31 04:28发布

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?

19条回答
余生无你
2楼-- · 2018-12-31 04:57

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.

查看更多
初与友歌
3楼-- · 2018-12-31 04:59

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

If you're getting the error in several places, and want to use the C macro system to hide the pragmas, you can define a macro to make it easier to suppress the warning:

#define SuppressPerformSelectorLeakWarning(Stuff) \
    do { \
        _Pragma("clang diagnostic push") \
        _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
        Stuff; \
        _Pragma("clang diagnostic pop") \
    } while (0)

You can use the macro like this:

SuppressPerformSelectorLeakWarning(
    [_target performSelector:_action withObject:self]
);

If you need the result of the performed message, you can do this:

id result;
SuppressPerformSelectorLeakWarning(
    result = [_target performSelector:_action withObject:self]
);
查看更多
倾城一夜雪
4楼-- · 2018-12-31 04:59

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.

查看更多
牵手、夕阳
5楼-- · 2018-12-31 05:02

Here is an updated macro based on the answer given above. This one should allow you to wrap your code even with a return statement.

#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]
);
查看更多
永恒的永恒
6楼-- · 2018-12-31 05:03

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".

查看更多
流年柔荑漫光年
7楼-- · 2018-12-31 05:05

You could also use a protocol here. So, create a protocol like so:

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

In your class that needs to call your selector, you then have a @property.

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

When you need to call @selector(doSomethingWithObject:) in an instance of MyObject, do this:

[self.source doSomethingWithObject:object];
查看更多
登录 后发表回答