That's a topic called Higher Order Messaging in Cocoa, and developed by many people on the web. Start from here and try googling more. They add a category method to NSArray
so that you can do
NSArray*transformed=[[anArray map] stringByDeletingPathExtension];
The idea is as follows:
[anArray map]
creates a temporary object (say hom
)
hom
receives the message stringByDeletingPathExtension
hom
re-sends the message to all the elements of anArray
hom
collects the results and returns the resulting array.
If you just want a quick transform, I would define a category method:
@interface NSArray (myTransformingAddition)
-(NSArray*)my_arrayByApplyingBlock:(id(^)(id))block;
@end
@implementation NSArray (myTransformingAddition)
-(NSArray*)my_arrayByApplyingBlock:(id(^)(id))block{
NSMutableArray*result=[NSMutableArray array];
for(id x in self){
[result addObject:block(x)];
}
return result;
}
@end
Then you can do
NSArray* transformed=[anArray my_arrayByApplyingBlock:^id(id x){return [x stringByDeletingPathExtension];}];
Note the construct ^ return-type (arguments) { ...}
which creates a block. The return-type can be omitted, and clang
is quite smart on guessing it, but gcc
is quite strict about it and needs to be specified sometime. (In this case, it's guessed from the return
statement which has [x stringBy...]
which returns an NSString*
. So GCC guesses the return type of the block to be NSString*
instead of id
, which GCC thinks is incompatible, thus comes the error. )
On OS X Leopard or iOS 3, you can use PLBlocks to support blocks. My personal subjective opinion is that people who care about new software typically upgrade to the newest OS, so supporting the latest OS should be just fine; supporting an older OS won't increase your customer by a factor of two...
THAT SAID, there's already a nice open-source framework which does all I said above. See the discussion here, and especially the FunctionalKit linked there.
More addition: it's in fact easy to realize your pseudocode [array transform:stringByDeletingPathExtension]
.
@interface NSArray (myTransformingAddition)
-(NSArray*)my_transformUsingSelector:(SEL)sel;
@end
@implementation NSArray (myTransformingAddition)
-(NSArray*)my_transformUsingSelector:(SEL)sel;{
NSMutableArray*result=[NSMutableArray array];
for(id x in self){
[result addObject:[x performSelector:sel withObject:nil]];
}
return result;
}
@end
Then you can use it as follows:
NSArray*transformed=[array my_transformUsingSelector:@selector(stringByDeletingPathExtension)];
However I don't like it so much; you need to have a method already defined on the object in the array to use this method. For example, if NSString
doesn't have the operation what you want to do as a method, what would you do in this case? You need to first add it to NSString
via a category:
@interface NSString (myHack)
-(NSString*)my_NiceTransformation;
@end
@implementation NSString (myHack)
-(NSString*)my_NiceTransformation{
... computes the return value from self ...
return something;
}
@end
Then you can use
NSArray*transformed=[array my_transformUsingSelector:@selector(my_NiceTransformation)];
But it tends to be very verbose, because you need to define the method in other places first. I prefer providing what I want to operate directly at the call site, as in
NSArray*transformed=[array my_arrayByApplyingBlock:^id(id x){
... computes the return value from x ...
return something;
}];
Finally, never add category methods which do not start with a prefix like my_
or whatever. For example, in the future Apple might provide a nice method called transform
which does exactly what you want. But if you have a method called transform
in the category already, that will lead to an undefined behavior. In fact, it can happen that there is a private method by Apple already in the class.