0
votes

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.

2
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
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
also, nowhere in my posted code have I written @selector(SEL).karan satia
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
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

2 Answers

2
votes

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

0
votes

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.