Objective-C: Passing an IF statement as a argument - objective-c

I need to pass an IF statement to a method. In JavaScript you can assign a function to a variable. Then that variable can be passed to a function and executed. Does this exist in Objective-C?
This is the pattern I'd like to implement:
-(void)singleComparisonWith:(NSArray *)data
IndexBegin:(NSUInteger)indexBegin
IndexEnd:(NSUInteger)indexEnd
Threshold:(float)threshold {
NSIndexSet *set1 = [self searchWithData:data
Range:[self makeInspectionWithRange:indexBegin
End:indexEnd]
Option:NSEnumerationConcurrent
Comparison:XXXXXXXXX];
// XXXXXXXXX is an IF statement that looks for value at an index above threshold
}
-(void)rangeComparisonWith:(NSArray *)data
IndexBegin:(NSUInteger)indexBegin
IndexEnd:(NSUInteger)indexEnd
ThresholdLow:(float)thresholdLow
ThresholdHigh:(float)thresholdHigh {
NSIndexSet *candidates = [self searchWithData:data
Range:[self makeInspectionWithRange:indexBegin
End:indexEnd]
Option:NSEnumerationReverse
Comparison:YYYYYYYYY];
// YYYYYYYYY is an IF statement that looks for value at an index above thresholdLow and above thresholdHigh
}
-(NSIndexSet *)searchWithData:data
Range:(NSIndexSet *)range
Option:(NSEnumerationOptions)option
Comparison:(id)comparison {
return [data indexesOfObjectsAtIndexes:range
options:option
passingTest:^(id obj, NSUInteger idx, BOOL *stop){
// Comparison is used here. Returns YES if conditions(s) are met.
}
];
}
EDIT:
Here's the solution thanks to #Charles Srstka.
NSIndexSet *set1 = [self searchWithData:data
Range:[self makeInspectionWithRange:indexBegin
End:indexEnd]
Option:NSEnumerationConcurrent
Comparison:BOOL^(id o) {
return ([o floatValue] > threshold);
}
];
-(NSIndexSet *)searchWithData:data
Range:(NSIndexSet *)range
Option:(NSEnumerationOptions)option
Comparison:(BOOL(^)(id o))comparison {
return [data indexesOfObjectsAtIndexes:range
options:option
passingTest:^(id obj, NSUInteger idx, BOOL *stop){
return comparison(obj);
}
];
No errors in that segment.
Thank you for your help.

What you want in Objective-C is called block syntax. While certainly not the nicest thing to look at, or the easiest thing to remember, it will do what you want.
// declares a block named 'foo' (yes, the variable name goes inside the parens)
NSUInteger (^foo)(NSString *) = ^(NSString *baz) {
return [baz length];
};
// now you can call foo like a function:
NSUInteger result = foo(#"hello world");
// or pass it to something else:
[someObject doSomethingWith:foo];
// A method that takes a block looks like this:
- (void)doSomethingWith:(NSUInteger (^)(NSString *))block;
This site is a handy "cheat sheet" that lists all the ways to declare a block in Objective-C. You will probably be referring to it often. The URL I linked to is a newer, work-friendly mirror. I'm sure you can guess the site's original URL if you think about it. ;-)
Basically whenever you see a ^ in Objective-C, you're looking at a block declaration. Unless, of course, you're looking at an XOR operation. But usually it's a block.
EDIT: Look at the site I linked to, where it says "as an argument to a method call." You need to declare it using that syntax, i.e.
... comparison: ^BOOL(id o) {
return ([o floatValue] > threshold);
}];
I know it's not the most intuitive syntax in the world, which is why that site is useful as a cheat sheet.
Also, unrelated to your issue, but Objective-C naming convention is to start the argument labels with lower-case letters; i.e. range:, options:, and comparison: rather than Range:, Option:, Comparison:.

Related

In Objective-C/C, can you write a function that combines 2 blocks?

I often find myself creating a "wrapper" block which just serves to execute a number of other blocks, usually with the same type signature.
Say I have 2 blocks with the same type signature:
MyBlockT block1 = ^(NSString *string, id object) {
//1 does some work
};
MyBlockT block2 = ^(NSString *string, id object) {
//2 does some other work
};
Is there some way to implement the magic function Combine() which would take 2 blocks:
MyBlockT combinedBlock = Combine(block1, block2); //hypothetical function
and be equivalent to doing:
MyBlockT combinedBlock = ^(NSString *string, id object) {
block1(string, object);
block2(string, object);
};
I know this only makes sense with blocks that return void, but that's all I'm interested in.
The Combine function needs only take in 2 blocks, if I have more I can just chain them. I'm at wits end on how to go about implementing this or whether it's even possible.
P.S. I wouldn't mind if the solution involved C macros
EDIT
I'd like to be able to use the resulting block as a method argument, e.g.:
[UIView animateWithDuration:1 animations:someCombinedBlock];
Is this what you are looking for?
MyBlockT CombineBlocks(MyBlockT block1, MyBlockT block2)
{
return [^(NSString *string, id object) {
block1(string, object);
block2(string, object);
} copy];
}
The function creates a new block that calls the two given blocks sequentially.
Now up on GitHub, WoolBlockInvocation!
This is a pair of classes, WSSBlockInvocation and WSSBlockSignature, along with some supporting code, that leverage libffi and the ObjC #encode strings which the compiler generates for Blocks to allow you to invoke a whole list of Blocks with the same set of arguments.
Any number of Blocks can be added to an invocation object, provided their signatures -- meaning return type and number and types of arguments -- match. After setting arguments on the invocation object, the Blocks can be invoked in turn, with the return values, if any, stored for later access.
The piece that you're particularly interested in, sewing that list of Blocks up into a single Block, is provided by the invocationBlock method of WSSBlockInvocation.
- (id)invocationBlock
{
return [^void (void * arg1, ...){
[self setRetainsArguments:YES];
va_list args;
va_start(args, arg1);
void * arg = arg1;
NSUInteger numArguments = [blockSignature numberOfArguments];
for( NSUInteger idx = 1; idx < numArguments; idx++ ){
[self setArgument:&arg atIndex:idx];
arg = va_arg(args, void *);
}
va_end(args);
[self invoke];
} copy];
}
This returns a Block that (ab)uses varargs functionality to defer assigning arguments until that encapsulating Block is actually invoked itself. You can thus do the following:
WSSBlockInvocation * invocation = [WSSBlockInvocation invocationWithBlocks:#[animationBlockOne, animationBlockTwo]];
void (^combinedAnimation)(void) = [invocation invocationBlock];
[UIView animateWithDuration:1 animations:combinedAnimation];
Of course, if you're just worried about Blocks for animations, that take no arguments and have no return value, constructing a wrapper Block is trivial:
void (^combinedAnimation)(void) = ^{
animationBlock();
anotherAnimationBlock();
// etc.
};
You only need my code if you need to wrap a set of Blocks and invoke them all with the same set of arguments.
N.B. I have tested this on OS X on x86_64, but not on any other platform. I hope it works on ARM under iOS, but varargs is famously "not portable" and it may not. Caveat compilor, and let me know if something breaks.
Here is a fun abuse of varargs:
id combine(id block, ...)
{
NSMutableArray *blocks = [NSMutableArray array];
//[blocks addObject:block];
va_list objlist;
va_start(objlist, block);
//while((obj = va_arg(ap, id))) { // }
for(id obj = block; obj; obj = va_arg(objlist, id)) {
[blocks addObject:[obj copy]];
}
va_end(objlist);
void (^wrapper)(id,...) = ^(id arg, ...) {
NSMutableArray *args = [NSMutableArray array];
va_list arglist;
va_start(arglist, arg);
for(id x = arg; x; x = va_arg(arglist, id)) {
[args addObject:x];
}
va_end(arglist);
for(void (^blk)() in blocks) {
blk(args);
}
};
return [wrapper copy];
}
int main() {
NSString *fmt = #"-%d-\n%#\n---";
void (^foo)() = combine(^(NSArray *a){ NSLog(fmt, 1, a); },
^(NSArray *a){ NSLog(fmt, 2, a); }, nil);
foo(#"first", #"second", nil);
return 0;
}
You must define each block to accept an NSArray of arguments, and both the combine and resulting block invocation must have at least one argument and end in nil.
If you know the method signature ahead of time, you can work around the NSArray and block arguments restriction by altering the wrapper block appropriately.
Since you don't mind macros
#define combinedBlock(string, object) \
block1((string), (object) ) \
block2((string), (object) )
if you need to perform 2 or more animations simultaneously then RZViewActions is everything you need. Its code looks like almost as animateWithDuration:... calls but with additional features.
If you need to perform ANY blocks simultaneously then you need something like ReactiveCocoa. But I suggest you PromiseKit (simply because it is easier).

Objective C - what is the usage of a non-void block?

I've seen many blocks with void return type. But it's possible to declare non-void blocks. Whats the usage of this?
Block declaration,
-(void)methodWithBock:(NSString *(^)(NSString *str))block{
// do some work
block(#"string for str"); // call back
}
Using the method,
[self methodWithBock:^NSString *(NSString *str) {
NSLog(str); // prints call back
return #"ret val"; // <- return value for block
}];
In above block declaration , what exactly is the purpose of NSString return type of the block? How the return value ( "ret val") can be used ?
You can use non-void blocks for the same reason you'd use a non-void function pointer - to provide an extra level of indirection when it comes to code execution.
NSArray's sortUsingComparator provides one example of such use:
NSArray *sorted = [originalArray sortedArrayUsingComparator:(NSComparator)^(id obj1, id obj2){
NSString *lhs = [obj1 stringAttribute];
NSString *rhs = [obj2 stringAttribute];
return [lhs caseInsensitiveCompare:rhs];
}];
The comparator block lets you encapsulate the comparison logic outside the sortedArrayUsingComparator method that performs the sorting.
It's just a return, so you could do something like this to take advantage of the return value and do work on it as well.
-(void)methodWithBlock:(NSString *(^)(NSString *str))block{
// do some work
NSString *string = block(#"string for str"); // call back
// do something with the return string
NSLog(#"%#",string);
}

Is this an inefficient way of using fast enumeration?

I don't entirely understand the details of how fast enumeration works, but compare the following two cases:
for(NSObject *object in self.myParent.parentsParents.granfathersMother.cousin.unclesNephew.array) {
// do something
}
vs.
NSArray *array = self.myParent.parentsParents.granfathersMother.cousin.unclesNephew.array;
for(NSObject *object in array) {
// do something
}
In the first example, will it go through that entire chain every iteration to get the array? Should I be using the second way?
I was at WWDC when Apple introduced Fast Enumeration, and (I recall) we were told then that the right hand object is moved into a temp. In addition, it must be since this works:
for(id foo in [myCollection reverseObjectEnumerator])
You can see that collections that perform fast enumeration adopt the "Fast Enumeration Protocol" (NSFastEnumeration), which has one method:
– countByEnumeratingWithState:objects:count:
That method returns a C Array of objects that lets the enumeration go very quickly, again supporting the one time use of the right side.
Now, having said all that, currently Apple advises developers (at WWDC) to use the block enumeration, which they claim is both faster and generates less code:
[myCollection enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
... your code
} ];
What I am fond of doing is not using "id obj", but the actual type (to avoid a cast in the block):
[myCollection enumerateObjectsUsingBlock:^(NSDictionary *dict, NSUInteger idx, BOOL *stop)
{
... your code
} ];
Neither the compiler nor the analyzer (4.4) complains when I do this.
If you need to set a variable outside this method, then you have to make it a block variable:
__block int foo = 0;
[myCollection enumerateObjectsUsingBlock:^(NSNumber *num, NSUInteger idx, BOOL *stop)
{
foo = MAX(foo, [num integerValue]);
} ];
EDIT: as a clarification, the direct answer to your question is 'no', the statement 'self.myParent.parentsParents.granfathersMother.cousin.unclesNephew.array' is evaluated once, and the final object stored as a temp on the stack. Also, you can use the same technique with block enumeations - the statement is evaluated once and the final returned object used for the enumeration.
__block int foo = 0;
[self.myParent.parentsParents.granfathersMother.cousin.unclesNephew.array enumerateObjectsUsingBlock:^(NSNumber *num, NSUInteger idx, BOOL *stop)
{
foo = MAX(foo, [num integerValue]);
} ];
EDIT2: I found another thread on SO where this same topic was discussed. The one point I missed regarding block enumeration is that you can specify that they should be run concurrently (or in reverse) using the slightly more complex method:
enumerateObjectsWithOptions:usingBlock:
As iOS devices get more and more core's this could potentially be a big win depending on what you're doing.
#bbum's response to the question (and others too) are here.
That's probably compiler-specific (i.e. undefined). If you are that bothered then add some timing code and find out yourself:
#import <sys/time.h>
static unsigned getTickCount()
{
struct timeval tv;
gettimeofday(&tv, 0);
return (unsigned)((tv.tv_sec * 1000) + (tv.tv_usec / 1000));
}
...
unsigned startTime = getTickCount();
for(NSObject *object in self.myParent.parentsParents.granfathersMother.cousin.unclesNephew.array) {
// do something
}
unsigned endTime = getTickCount();
NSLog(#"That took %umS", endTime - startTime);
You will have to have a pretty big array however in order to register anything above 0.

Objective c implement method which takes array of arguments

Hee
Does anybody know how to implement an method in objective c that will take an array of arguments as parameter such as:
[NSArray arrayWithObjects:#"A",#"B",nil];
The method declaration for this method is:
+ (id)arrayWithObjects:(id)firstObj...
I can't seem to make such method on my own. I did the following:
+ (void) doSometing:(id)string manyTimes:(NSInteger)numberOfTimes;
[SomeClass doSometing:#"A",#"B",nil manyTimes:2];
It will give the warningtoo many arguments to function 'doSometing:manyTimes:'
Thanks already.
The ellipsis (...) is inherited from C; you can use it only as the final argument in a call (and you've missed out the relevant comma in your example). So in your case you'd probably want:
+ (void)doSomethingToObjects:(id)firstObject, ...;
or, if you want the count to be explicit and can think of a way of phrasing it well:
+ (void)doManyTimes:(NSInteger)numberOfTimes somethingToObjects:(id)firstObject, ...;
You can then use the normal C methods for dealing with ellipses, which reside in stdarg.h. There's a quick documentation of those here, example usage would be:
+ (void)doSomethingToObjects:(id)firstObject, ...
{
id object;
va_list argumentList;
va_start(argumentList, firstObject);
object = firstObject;
while(1)
{
if(!object) break; // we're using 'nil' as a list terminator
[self doSomethingToObject:object];
object = va_arg(argumentList, id);
}
va_end(argumentList);
}
EDIT: additions, in response to comments. You can't pass the various things handed to you in an ellipsis to another function that takes an ellipsis due to the way that C handles function calling (which is inherited by Objective-C, albeit not obviously so). Instead you tend to pass the va_list. E.g.
+ (NSString *)doThis:(SEL)selector makeStringOfThat:(NSString *)format, ...
{
// do this
[self performSelector:selector];
// make string of that...
// get the argument list
va_list argumentList;
va_start(argumentList, format);
// pass it verbatim to a suitable method provided by NSString
NSString *string = [[NSString alloc] initWithFormat:format arguments:argumentList];
// clean up
va_end(argumentList);
// and return, as per the synthetic example
return [string autorelease];
}
Multiple arguments (also known as an arglist) can only come at the end of a method declaration. Your doSomething method would look something like this:
+ (void)doNumberOfTimes:(NSInteger)numberOfTimes withStrings:(id)firstArg, ...
{
va_list args;
va_start(args, firstArg);
NSString * argString = firstArg;
while (argString != nil)
{
// do something with argString here
argString = va_arg(args, NSString *);
}
va_end(args);
}
To be called as follows:
[SomeClass doNumberOfTimes:2 withStrings:#"A", #"B", nil];
See also: How to create variable argument methods in Objective-C
I think you're after a variadic function. Here's Apple's documentation: http://developer.apple.com/library/mac/qa/qa2005/qa1405.html

Objective-C switch using objects?

I'm doing some Objective-C programming that involves parsing an NSXmlDocument and populating an objects properties from the result.
First version looked like this:
if([elementName compare:#"companyName"] == 0)
[character setCorporationName:currentElementText];
else if([elementName compare:#"corporationID"] == 0)
[character setCorporationID:currentElementText];
else if([elementName compare:#"name"] == 0)
...
But I don't like the if-else-if-else pattern this produces. Looking at the switch statement I see that i can only handle ints, chars etc and not objects... so is there a better implementation pattern I'm not aware of?
BTW I did actually come up with a better solution for setting the object's properties, but I want to know specifically about the if-else vs switch pattern in Objective-C
You should take advantage of Key-Value Coding:
[character setValue:currentElementText forKey:elementName];
If the data is untrusted, you might want to check that the key is valid:
if (![validKeysCollection containsObject:elementName])
// Exception or error
I hope you'll all forgive me for going out on a limb here, but I would like to address the more general question of parsing XML documents in Cocoa without the need of if-else statements. The question as originally stated assigns the current element text to an instance variable of the character object. As jmah pointed out, this can be solved using key-value coding. However, in a more complex XML document this might not be possible. Consider for example the following.
<xmlroot>
<corporationID>
<stockSymbol>EXAM</stockSymbol>
<uuid>31337</uuid>
</corporationID>
<companyName>Example Inc.</companyName>
</xmlroot>
There are multiple approaches to dealing with this. Off of the top of my head, I can think of two using NSXMLDocument. The first uses NSXMLElement. It is fairly straightforward and does not involve the if-else issue at all. You simply get the root element and go through its named elements one by one.
NSXMLElement* root = [xmlDocument rootElement];
// Assuming that we only have one of each element.
[character setCorperationName:[[[root elementsForName:#"companyName"] objectAtIndex:0] stringValue]];
NSXMLElement* corperationId = [root elementsForName:#"corporationID"];
[character setCorperationStockSymbol:[[[corperationId elementsForName:#"stockSymbol"] objectAtIndex:0] stringValue]];
[character setCorperationUUID:[[[corperationId elementsForName:#"uuid"] objectAtIndex:0] stringValue]];
The next one uses the more general NSXMLNode, walks through the tree, and directly uses the if-else structure.
// The first line is the same as the last example, because NSXMLElement inherits from NSXMLNode
NSXMLNode* aNode = [xmlDocument rootElement];
while(aNode = [aNode nextNode]){
if([[aNode name] isEqualToString:#"companyName"]){
[character setCorperationName:[aNode stringValue]];
}else if([[aNode name] isEqualToString:#"corporationID"]){
NSXMLNode* correctParent = aNode;
while((aNode = [aNode nextNode]) == nil && [aNode parent != correctParent){
if([[aNode name] isEqualToString:#"stockSymbol"]){
[character setCorperationStockSymbol:[aNode stringValue]];
}else if([[aNode name] isEqualToString:#"uuid"]){
[character setCorperationUUID:[aNode stringValue]];
}
}
}
}
This is a good candidate for eliminating the if-else structure, but like the original problem, we can't simply use switch-case here. However, we can still eliminate if-else by using performSelector. The first step is to define the a method for each element.
- (NSNode*)parse_companyName:(NSNode*)aNode
{
[character setCorperationName:[aNode stringValue]];
return aNode;
}
- (NSNode*)parse_corporationID:(NSNode*)aNode
{
NSXMLNode* correctParent = aNode;
while((aNode = [aNode nextNode]) == nil && [aNode parent != correctParent){
[self invokeMethodForNode:aNode prefix:#"parse_corporationID_"];
}
return [aNode previousNode];
}
- (NSNode*)parse_corporationID_stockSymbol:(NSNode*)aNode
{
[character setCorperationStockSymbol:[aNode stringValue]];
return aNode;
}
- (NSNode*)parse_corporationID_uuid:(NSNode*)aNode
{
[character setCorperationUUID:[aNode stringValue]];
return aNode;
}
The magic happens in the invokeMethodForNode:prefix: method. We generate the selector based on the name of the element, and perform that selector with aNode as the only parameter. Presto bango, we've eliminated the need for an if-else statement. Here's the code for that method.
- (NSNode*)invokeMethodForNode:(NSNode*)aNode prefix:(NSString*)aPrefix
{
NSNode* ret = nil;
NSString* methodName = [NSString stringWithFormat:#"%#%#:", prefix, [aNode name]];
SEL selector = NSSelectorFromString(methodName);
if([self respondsToSelector:selector])
ret = [self performSelector:selector withObject:aNode];
return ret;
}
Now, instead of our larger if-else statement (the one that differentiated between companyName and corporationID), we can simply write one line of code
NSXMLNode* aNode = [xmlDocument rootElement];
while(aNode = [aNode nextNode]){
aNode = [self invokeMethodForNode:aNode prefix:#"parse_"];
}
Now I apologize if I got any of this wrong, it's been a while since I've written anything with NSXMLDocument, it's late at night and I didn't actually test this code. So if you see anything wrong, please leave a comment or edit this answer.
However, I believe I have just shown how properly-named selectors can be used in Cocoa to completely eliminate if-else statements in cases like this. There are a few gotchas and corner cases. The performSelector: family of methods only takes 0, 1, or 2 argument methods whose arguments and return types are objects, so if the types of the arguments and return type are not objects, or if there are more than two arguments, then you would have to use an NSInvocation to invoke it. You have to make sure that the method names you generate aren't going to call other methods, especially if the target of the call is another object, and this particular method naming scheme won't work on elements with non-alphanumeric characters. You could get around that by escaping the XML element names in your method names somehow, or by building an NSDictionary using the method names as the keys and the selectors as the values. This can get pretty memory intensive and end up taking a longer time. performSelector dispatch like I described is pretty fast. For very large if-else statements, this method may even be faster than an if-else statement.
If you want to use as little code as possible, and your element names and setters are all named so that if elementName is #"foo" then setter is setFoo:, you could do something like:
SEL selector = NSSelectorFromString([NSString stringWithFormat:#"set%#:", [elementName capitalizedString]]);
[character performSelector:selector withObject:currentElementText];
or possibly even:
[character setValue:currentElementText forKey:elementName]; // KVC-style
Though these will of course be a bit slower than using a bunch of if statements.
[Edit: The second option was already mentioned by someone; oops!]
Dare I suggest using a macro?
#define TEST( _name, _method ) \
if ([elementName isEqualToString:# _name] ) \
[character _method:currentElementText]; else
#define ENDTEST { /* empty */ }
TEST( "companyName", setCorporationName )
TEST( "setCorporationID", setCorporationID )
TEST( "name", setName )
:
:
ENDTEST
One way I've done this with NSStrings is by using an NSDictionary and enums. It may not be the most elegant, but I think it makes the code a little more readable. The following pseudocode is extracted from one of my projects:
typedef enum { UNKNOWNRESIDUE, DEOXYADENINE, DEOXYCYTOSINE, DEOXYGUANINE, DEOXYTHYMINE } SLSResidueType;
static NSDictionary *pdbResidueLookupTable;
...
if (pdbResidueLookupTable == nil)
{
pdbResidueLookupTable = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithInteger:DEOXYADENINE], #"DA",
[NSNumber numberWithInteger:DEOXYCYTOSINE], #"DC",
[NSNumber numberWithInteger:DEOXYGUANINE], #"DG",
[NSNumber numberWithInteger:DEOXYTHYMINE], #"DT",
nil];
}
SLSResidueType residueIdentifier = [[pdbResidueLookupTable objectForKey:residueType] intValue];
switch (residueIdentifier)
{
case DEOXYADENINE: do something; break;
case DEOXYCYTOSINE: do something; break;
case DEOXYGUANINE: do something; break;
case DEOXYTHYMINE: do something; break;
}
The if-else implementation you have is the right way to do this, since switch won't work with objects. Apart from maybe being a bit harder to read (which is subjective), there is no real downside in using if-else statements this way.
Although there's not necessarily a better way to do something like that for one time use, why use "compare" when you can use "isEqualToString"? That would seem to be more performant since the comparison would halt at the first non-matching character, rather than going through the whole thing to calculate a valid comparison result (though come to think of it the comparison might be clear at the same point) - also though it would look a little cleaner because that call returns a BOOL.
if([elementName isEqualToString:#"companyName"] )
[character setCorporationName:currentElementText];
else if([elementName isEqualToString:#"corporationID"] )
[character setCorporationID:currentElementText];
else if([elementName isEqualToString:#"name"] )
There is actually a fairly simple way to deal with cascading if-else statements in a language like Objective-C. Yes, you can use subclassing and overriding, creating a group of subclasses that implement the same method differently, invoking the correct implementation at runtime using a common message. This works well if you wish to choose one of a few implementations, but it can result in a needless proliferation of subclasses if you have many small, slightly different implementations like you tend to have in long if-else or switch statements.
Instead, factor out the body of each if/else-if clause into its own method, all in the same class. Name the messages that invoke them in a similar fashion. Now create an NSArray containing the selectors of those messages (obtained using #selector()). Coerce the string you were testing in the conditionals into a selector using NSSelectorFromString() (you may need to concatenate additional words or colons to it first depending on how you named those messages, and whether or not they take arguments). Now have self perform the selector using performSelector:.
This approach has the downside that it can clutter-up the class with many new messages, but it's probably better to clutter-up a single class than the entire class hierarchy with new subclasses.
Posting this as a response to Wevah's answer above -- I would've edited, but I don't have high enough reputation yet:
unfortunately the first method breaks for fields with more than one word in them -- like xPosition. capitalizedString will convert that to Xposition, which when combined with the format give you setXposition: . Definitely not what was wanted here. Here is what I'm using in my code:
NSString *capName = [elementName stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[elementName substringToIndex:1] uppercaseString]];
SEL selector = NSSelectorFromString([NSString stringWithFormat:#"set%#:", capName]);
Not as pretty as the first method, but it works.
I have come up with a solution that uses blocks to create a switch-like structure for objects. There it goes:
BOOL switch_object(id aObject, ...)
{
va_list args;
va_start(args, aObject);
id value = nil;
BOOL matchFound = NO;
while ( (value = va_arg(args,id)) )
{
void (^block)(void) = va_arg(args,id);
if ( [aObject isEqual:value] )
{
block();
matchFound = YES;
break;
}
}
va_end(args);
return matchFound;
}
As you can see, this is an oldschool C function with variable argument list. I pass the object to be tested in the first argument, followed by the case_value-case_block pairs. (Recall that Objective-C blocks are just objects.) The while loop keeps extracting these pairs until the object value is matched or there are no cases left (see notes below).
Usage:
NSString* str = #"stuff";
switch_object(str,
#"blah", ^{
NSLog(#"blah");
},
#"foobar", ^{
NSLog(#"foobar");
},
#"stuff", ^{
NSLog(#"stuff");
},
#"poing", ^{
NSLog(#"poing");
},
nil); // <-- sentinel
// will print "stuff"
Notes:
this is a first approximation without any error checking
the fact that the case handlers are blocks, requires additional care when it comes to visibility, scope and memory management of variables referenced from within
if you forget the sentinel, you are doomed :P
you can use the boolean return value to trigger a "default" case when none of the cases have been matched
The most common refactoring suggested for eliminating if-else or switch statements is introducing polymorphism (see http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html). Eliminating such conditionals is most important when they are duplicated. In the case of XML parsing like your sample you are essentially moving the data to a more natural structure so that you won't have to duplicate the conditional elsewhere. In this case the if-else or switch statement is probably good enough.
In this case, I'm not sure if you can easily refactor the class to introduce polymorphism as Bradley suggests, since it's a Cocoa-native class. Instead, the Objective-C way to do it is to use a class category to add an elementNameCode method to NSSting:
typedef enum {
companyName = 0,
companyID,
...,
Unknown
} ElementCode;
#interface NSString (ElementNameCodeAdditions)
- (ElementCode)elementNameCode;
#end
#implementation NSString (ElementNameCodeAdditions)
- (ElementCode)elementNameCode {
if([self compare:#"companyName"]==0) {
return companyName;
} else if([self compare:#"companyID"]==0) {
return companyID;
} ... {
}
return Unknown;
}
#end
In your code, you could now use a switch on [elementName elementNameCode] (and gain the associated compiler warnings if you forget to test for one of the enum members etc.).
As Bradley points out, this may not be worth it if the logic is only used in one place.
What we've done in our projects where we need to so this sort of thing over and over, is to set up a static CFDictionary mapping the strings/objects to check against to a simple integer value. It leads to code that looks like this:
static CFDictionaryRef map = NULL;
int count = 3;
const void *keys[count] = { #"key1", #"key2", #"key3" };
const void *values[count] = { (uintptr_t)1, (uintptr_t)2, (uintptr_t)3 };
if (map == NULL)
map = CFDictionaryCreate(NULL,keys,values,count,&kCFTypeDictionaryKeyCallBacks,NULL);
switch((uintptr_t)CFDictionaryGetValue(map,[node name]))
{
case 1:
// do something
break;
case 2:
// do something else
break;
case 3:
// this other thing too
break;
}
If you're targeting Leopard only, you could use an NSMapTable instead of a CFDictionary.
Similar to Lvsti I am using blocks to perform a switching pattern on objects.
I wrote a very simple filter block based chain, that takes n filter blocks and performs each filter on the object.
Each filter can alter the object, but must return it. No matter what.
NSObject+Functional.h
#import <Foundation/Foundation.h>
typedef id(^FilterBlock)(id element, NSUInteger idx, BOOL *stop);
#interface NSObject (Functional)
-(id)processByPerformingFilterBlocks:(NSArray *)filterBlocks;
#end
NSObject+Functional.m
#implementation NSObject (Functional)
-(id)processByPerformingFilterBlocks:(NSArray *)filterBlocks
{
__block id blockSelf = self;
[filterBlocks enumerateObjectsUsingBlock:^( id (^block)(id,NSUInteger idx, BOOL*) , NSUInteger idx, BOOL *stop) {
blockSelf = block(blockSelf, idx, stop);
}];
return blockSelf;
}
#end
Now we can set up n FilterBlocks to test for the different cases.
FilterBlock caseYES = ^id(id element, NSUInteger idx, BOOL *breakAfter){
if ([element isEqualToString:#"YES"]) {
NSLog(#"You did it");
*breakAfter = YES;
}
return element;
};
FilterBlock caseNO = ^id(id element, NSUInteger idx, BOOL *breakAfter){
if ([element isEqualToString:#"NO"] ) {
NSLog(#"Nope");
*breakAfter = YES;
}
return element;
};
Now we stick those block we want to test as a filter chain in an array:
NSArray *filters = #[caseYES, caseNO];
and can perform it on an object
id obj1 = #"YES";
id obj2 = #"NO";
[obj1 processByPerformingFilterBlocks:filters];
[obj2 processByPerformingFilterBlocks:filters];
This approach can be used for switching but also for any (conditional) filter chain application, as the blocks can edit the element and pass it on.