Search code examples
objective-cshort-circuiting

Does Objective-C use short-circuit evaluation for messages to nil objects?


Following the usual short-circuit evaluation question, does short-circuit evaluation work for parameters built and sent against nil objects? Example:

NSMutableArray *nil_array = nil;
....
[nil_array addObject:[NSString stringWithFormat:@"Something big %@",
     function_that_takes_a_lot_of_time_to_compute()]];

Is that slow function going to be called or will the whole addObject call be optimized out without processing the parameters?


Solution

  • A message is always dispatched to an object pointer, regardless of whether it points to an object or points to nil. Additionally, messages are sent in the runtime and therefore the compiler cannot just assume nil_array really is nil and optimize it away. What if the initialization did something else, and nil_array turns out to be an instance?

    That means all the expressions you pass as arguments to your methods will be evaluated in order to be passed, so no short-circuiting of any sort happens. Your slow function will be executed, and if it takes a long time it'll affect the performance of your program.

    EDIT: I just whipped up a little test case for the heck of it (empty Objective-C command line program). If you run this and observe the debugger console, you'll notice that output from all three calls to function_that_takes_a_lot_of_time_to_compute() appears (in 5-second intervals), while output from only t1's and t3's test: methods appears — naturally, since these are not nil.

    main.m

    #import "Test.h"
    
    int function_that_takes_a_lot_of_time_to_compute(void)
    {
        static int i = 1;
    
        sleep(5);
        NSLog(@"%d", i);
    
        return i++;
    }
    
    int main(int argc, const char *argv[])
    {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
        Test *t1 = [[Test alloc] init], *t2 = nil, *t3 = [[Test alloc] init];
    
        [t1 test:function_that_takes_a_lot_of_time_to_compute()];
        [t2 test:function_that_takes_a_lot_of_time_to_compute()]; // nil
        [t3 test:function_that_takes_a_lot_of_time_to_compute()];
    
        [t1 release];
        [t3 release];
    
        [pool drain];
        return 0;
    }
    

    Test.h

    @interface Test : NSObject {}
    
    - (void)test:(int)arg;
    
    @end
    

    Test.m

    @implementation Test
    
    - (void)test:(int)arg
    {
        NSLog(@"Testing arg: %d", arg);
    }
    
    @end
    

    Output

    1
    Testing arg: 1
    2
    3
    Testing arg: 3