Block-Closures and ternary operator for enumeration of NSArray with dynamic code

557 views Asked by At

I have no clue if what I am trying to do is possible or not. I am not experienced with blocks, just read a few tutorials, and really like the idea. My class AppointmentView.h (and m) is extending a ViewController. It contains a few properties which are members of another Class Appointment, which in turn has a few NSArrays. For the purpose of creating a grouped tableview, I check the values of the arrays, and then copy appropriately values that will be displayed (nothing too fancy or complicated). Normally I would go with if-else statements, and control appropriately, however after looking at blocks, it got me thinking if the following is doable:

[self setOutcomes: [[NSArray alloc] initWithObjects:
                        ^{ return [myAppointment.CANCELED isEqualToString:@"NO"] == YES ? 
                                                            [[NSString alloc] initWithString:@"Not Cancelled"] :
                                                            [[NSString alloc] initWithString:@"Cancelled"]; },
                        ^{ return [myAppointment.CANCELED isEqualToString:@"NO"] == YES ?
                                                            [[NSString alloc] initWithString:@"No Cancellation reason"] :
                                                            [[NSString alloc] initWithString:myAppointment.CANCREASON]; },
                        ^{ return [myAppointment EVENTS].length > 0 ?
                                                            [[NSString alloc] initWithString:myAppointment.EVENTS] :
                                                            [[NSString alloc] initWithString:@"No Events"]; },
                        ^{ return [myAppointment SUMMARY].length > 0 ?
                            [[NSString alloc] initWithString:myAppointment.SUMMARY] :
                            [[NSString alloc] initWithString:@"No Summary"]; },
                        nil]];

This code compiles fine, but it crashes when run. My understanding is that this is wrong, because I am telling the enumeration to execute a piece of code every time. So this is not what I want. Then I tried the following:

   [self setOutcomes: [[NSArray alloc] initWithObjects:
                        [[NSString alloc] initWithString: (NSString *) ^(void){ 
                                                                        return [myAppointment.CANCELED isEqualToString:@"NO"] == YES ?
                                                                                                        @"Not Cancelled" : @"Cancelled"; }],
                        [[NSString alloc] initWithString: (NSString *) ^(void){ 
                                                                        return [myAppointment.CANCELED isEqualToString:@"NO"] == YES ? 
                                                                                                        @"No Cancellation reason" : myAppointment.CANCREASON; }],
                        [[NSString alloc] initWithString: (NSString *) ^(void){ 
                                                                        return [myAppointment.EVENTS isEqualToString:@""] == YES ? 
                                                                                                        myAppointment.EVENTS : @"No Events"; }],
                        [[NSString alloc] initWithString: (NSString *) ^(void){ 
                                                                        return [myAppointment.SUMMARY isEqualToString:@""] == YES ?
                                                                                                        myAppointment.SUMMARY : @"No Summary"; }],
                        nil]];

Which although it compiles fine, crashes with the following error:

-[NSMallocBlock length]: unrecognized selector sent to instance 0x6bd68f0

I also tried typedefining a generic method which I could use, but didnt help either. So, I was wondering, is this possible ? I know that for this piece of code, I could simply use an if-else (like I normally would) and be done with it, but other circumstances, such a dynamic code generation option would be extremely useful.

I got the feeling that either NSString does not know how to handle this, and I should therefore extend it with an options lets say

initWithBlock:

Or I am missing something else here. Or it may just be not possible.... (?)

PS: I have just seen NSArray's enumerateUsingBlock: method, but I am not certain if this would serve my purpose in this case, as each entry in the array is completely different from the other, and a specific block would not fit the criteria.

2

There are 2 answers

4
Jeffery Thomas On BEST ANSWER

If you need to make simple choices in the initializer you don't need blocks.

[NSArray initWithObjects:
         ([myAppointment.CANCELED isEqualToString:@"NO"] ? @"Not Cancelled" : @"Cancelled"),
         ([myAppointment.CANCELED isEqualToString:@"NO"] ? @"No Cancellation reason" : myAppointment.CANCREASON),
         nil];

Use parens to enclose the ?: operator.


Updated to answer question of how to define and invoke a block at same spot

This unit test worked for me:

- (void)testBlockNow
{
    BOOL X = ^{ return YES; }();
    STAssertTrue(X, nil);
}

That's simple: ^{ … } to define the block, then () to invoke the block.

4
Jeffery Thomas On

Yes, you can store blocks in an array, but you will need to be clever about it.

typedef (void)(^X)();

X block1 = ^{ … };
X block2 = ^{ … };
X block3 = ^{ … };

NSArray *array = [NSArray initWithObjects:
                  [[block1 copy] autorelease],
                  [[block2 copy] autorelease],
                  [[block3 copy] autorelease],
                  nil];