0

I am familiar with the solutions to this ARC warning (performSelector may cause a leak because its selector is unknown) and have implemented them in most cases, but I can't seem to find a way to properly get the return value for a selector without just suppressing a warning.

It seems that maybe it can't or shouldn't be done, but a rewrite of code logic (developed by others) is too time consuming.

Code example:

NSString *message = [callback performSelector:validatorSel withObject:textCell.textField.text];
Community
  • 1
  • 1
user1122069
  • 1,767
  • 1
  • 24
  • 52
  • Don't use selectors and `performSelector:withObject:`. Use a protocol so you can call the method directly or use a block. Either is much preferred since they are clearer, safer, and easier. – rmaddy Oct 05 '16 at 00:46
  • What about the answer you linked is causing you a problem, doesn't it explain exactly what you are after using `methodForSelector` to replace `performSelector:withObject:`? Provided that is you wish to call a "normal" method which returns and unowned string. (I.e What am I missing in your question?) – CRD Oct 05 '16 at 11:28

2 Answers2

1

If validatorSel is known to not to begin with allocor new, or to have copy (or Copy) in its name, and you know there are no memory-management overrides involved (which are rare), then the default memory management will be correct here, and you can suppress the warning with an appropriate #pragma. If you cannot prove those things, then this may crash, which is why there's a warning.

If you cannot prove the above requirements, then there is no way to make this safe under ARC. You will either have to build it without ARC or rewrite it.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • I'm pretty sure you can call *alloc*, *init*, *copy* and *new* family methods via selectors safely under ARC, it is just a question of typing the function `IMP` correctly with the appropriate attributes (see my answer). Not that you'd normally call at least three of those families in this way... – CRD Oct 07 '16 at 21:55
0

From your code sample it looks like you are expecting a selector for a method which takes an NSText * and return an NSString *. So from your linked answer you can determine that the implementation of this method has the function type:

NSString *(*)(ID, SEL, NSText *)

Here ID may be replaced by the type of callback, and NSText * can be replaced by the actual type of textCell.textField.text if our guess is wring.

Again from your linked answer, you can obtain the implementation and call it using:

NSString *(*implementation)(ID, SEL, NSText *)
   = (void *)[callback methodForSelector: performSelector:validatorSel];
NSString *message = implementation(callback, validatorSel, textCell.textField.text);

As @RobNapier correctly points out this is only safe under ARC if the selector does not return a retained value, i.e. for normal[*] selectors if it is a member of the init, copy, or new method families. Now you are very unlikely to be passed an init family method for validatorSel as that would require callback to be a reference to an alloc'ed but not init'ed object, so we can ignore that one for now[#]. To test for the other two families you can use code along the lines of:

NSString *message; // for the return value of the selector
NSString *selName = NSStringFromSelector(validatorSel); // get string name of selector
if ([selName hasPrefix:@"new"]     // starts with new,
    || [selName hasPrefix:@"copy"] // or copy,
    || [selName rangeOfString:@"Copy"].location != NSNotFound) // or contains "Copy"
{
   // need to handle returning a retained object
   ...
}
else
{
   // normal case
   NSString *(*implementation)(ID, SEL, NSText *)
      = (void *)[callback methodForSelector: performSelector:validatorSel];
   message = implementation(callback, validatorSel, textCell.textField.text);
}

Which just leaves how to handle the return value correctly under ARC for copy and new family methods...

Handling copy and new family methods

ARC knows that a method, or function, returns a retained object by an attribute being placed on the method/function type. The naming convention is just the language's way of inferring the attribute if it is not present, it can be manually specified using the NS_RETURNS_RETAINED macro on a method/function declaration. So the missing code above is just:

{
   // need to handle returning a retained object
   NSString *(*implementation)(ID, SEL, NSText *) NS_RETURNS_RETAINED
      = (void *)[callback methodForSelector: performSelector:validatorSel];
   message = implementation(callback, validatorSel, textCell.textField.text);
}

The modified type for implementation tells ARC that it will return a retained object and ARC will handle the call the same way it does for known copy or new family methods.

HTH


Note: Handling init family methods

We skipped the init family not just because it is highly unlikely but also because it behaves differently - init family methods consume the object reference they are called on, that is they expect to be passed an owned object which they take ownership of, and will release it if needed. Unsurprisingly consuming an argument is also indicated by an attribute, just as for returning a retained object. The curious reader might wish to determine the code required, even though needing it is highly unlikely.


[*] A "normal" selector is one for a method which follows the standard naming conventions of Objective-C and does not use attributes to alter the memory ownership behaviour in ways contrary to the standard conventions. Only supporting standard conventions is not a big restriction, the whole point of the conventions is that code relies on them!

[#] You are of course very unlikely to be passed a new family selector as well, callback would usually have to be a reference to a class object, but handling it is the same as for the copy family so we've included it.

CRD
  • 52,522
  • 5
  • 70
  • 86