0

I was messing around and just tried implementing a simple non-block / delegate callback function.

Class A.m
@implementation noblocks
-(void)logSomethingAndNotify:(id)object andCallSelector:(SEL)selector {

    //some task
    NSLog(@"TRYING THIS OUT");

    //implement callback functionality
    if ([object respondsToSelector:@selector(selector)]) {
        [object performSelector:@selector(selector) withObject:object];
    }

}

@end

Class B.m
- (void)viewDidLoad {
    [super viewDidLoad];
    ClassA *noblock = [noblocks new];
    [ClassA logSomethingAndNotify:self andCallSelector:@selector(addSubviewAfterDelay)];
}

-(void)addSubviewAfterDelay {
    double delayInSeconds = 2.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        UIView *view = [[UIView alloc]initWithFrame:CGRectMake(50, 50, 100, 100)];
        view.backgroundColor = [UIColor blueColor];
        [self.view addSubview:view];
    });
}

In the implementation file of Class A, if I change this statement:

if ([object respondsToSelector:@selector(selector)]) {            
[object performSelector:@selector(selector) withObject:object];
   }

to the selector the way it is represented as a method parameter

if ([object respondsToSelector:selector]) {
[object performSelector:selector withObject:object];
}

then I get a memory leak warning from the compiler.

I understand that given the dynamic runtime of Objective-C, sending a selectorless message to an unknown object can potentially be problematic - we don't know the return type of the method and can't be sure that we should retain the object returned (if there is one). What I don't understand is why using performSelector:@selector(selector) vs just using performSelector:selector DOESN'T cause any ARC warning.

This question is NOT a duplicate of others addressing the compiler warning - my question is not about WHY the error is shown so much as why ONE way shows a warning while the OTHER does not.

karan satia
  • 307
  • 4
  • 16
  • 1
    Using `@selector(SEL)` isn't valid as the whole point of the `@selector` keyword is to generate/retrieve the `SEL` for the specified selector. Therefore the first case is invalid. The reason for the warning is well documented here and elsewhere. – trojanfoe Mar 02 '16 at 16:32
  • Invalid how? Both compile and run just fine, it's just that one throws a compiler error and I don't understand why the other one doesn't. It's also not well documented when comparing both cases; I've looked all over. The error message is addressed, not the comparison of cases. – karan satia Mar 02 '16 at 16:40
  • also, nowhere in my posted code have I written @selector(SEL). – karan satia Mar 02 '16 at 16:40
  • It's invalid as the argument to `@selector()` should be a method name. I used the notation `@selector(SEL)` to illustrate the fact that you are calling `@selector()` on a variable of type `SEL`. – trojanfoe Mar 02 '16 at 16:42
  • I see. Could you then explain why using @selector(SEL) throws no error while using performSelector:SEL does throw an ARC error? I understand why the entire thing shouldn't be used, it was just a little test. – karan satia Mar 02 '16 at 16:44
  • No I don't know why. The warning is valid, however, so needs to be heeded. – trojanfoe Mar 02 '16 at 16:44
  • Ok. Well my question was why that was happening - hence, not a duplicate question. Thanks for the input, though. Can you unmark my question as a duplicate so that somebody can possibly answer it? – karan satia Mar 02 '16 at 17:51
  • I cannot, sorry, only a mod can do that (you can call for mod assistance by flagging the question). – trojanfoe Mar 02 '16 at 18:09

2 Answers2

2

@selector(selector) isn't doing what you think it is. :)

Consider:

    SEL selector = @selector(hash);
    NSLog(@"%s %s", selector, @selector(selector));

This outputs (relying on the fact that an SEL is really a char* as an implementation detail that you should never rely on outside of experiments like this):

   asdfasdfa[71281:7385265] hash selector

@selector(selector) produces a constant value that doesn't trigger the ARC memory warning because the compiler can reason about the code path appropriately.

I.e. consider:

    SEL bob = @selector(dobbs);
    NSLog(@"%s %s", bob, @selector(bob));

Produces:

    asdfasdfa[71313:7394673] hobbs bob

The variable bob refers to the SEL dobbs whereas @selector(bob) produces the SEL bob.

bbum
  • 162,346
  • 23
  • 271
  • 359
  • To clarify, the reason @selector(selector) doesn't trigger a memory warning is because it checks for the NAME of the selector, whereas SEL causes the compiler to check for a memory address? Might be helpful if you separate the two outputs with a newline character. I tried logging it myself but the compiler was throwing me errors... – karan satia Mar 04 '16 at 18:24
  • Sort if; `@selector(selector)` is a static, compile time, expression. The value it produces is a constant and, thus, the compiler can reason about the method being called. The moment the SEL argument to `performSelector:` becomes dynamic, the compiler can no longer know that it won't produce a memory leak. – bbum Mar 04 '16 at 20:34
1

The reason why there is a warning when using performSelector: on a dynamic selector value, is that in Cocoa memory management, certain methods are meant to return a retained (+1) instance (i.e. it will be the caller's responsibility to release it). This is by default methods whose names start with alloc, retain, new, copy, or mutableCopy. All other methods return a non-retained instance (or retained and autoreleased; anything that doesn't require the caller to release).

performSelector:, by its name, is not implied to return a retained instance, and it's not annotated with ns_returns_retained. Therefore ARC will consider it to not return a retained instance (which is correct if you use it to perform "normal" methods). But since we don't know what the selector is at compile time, it could be that you are passing one of those selectors that return a retained instance, in which it would be wrong and cause a leak.

In the case where the selector is hard-coded at compile-time, the compiler can check the name at compile time and verify that it is not one of those that return a retained instance.

newacct
  • 119,665
  • 29
  • 163
  • 224