Higher Order Functions in Objective-C
Map, Filter, Reduce and FlatMap Implementation for NSArray

No, you did not get fooled by your eyes. The title is not a typo.
You may hear of functioning programming or higher order functions, like map, filter, reduce, flatMap, and etc., for Swift before. Trust me! If you start to use them in your projects today, you will love it. The approach to take functions as arguments in another function is so handy and succinct that it's very addictive and hard to reject.
And… what about Objective-C? Are we able to take the same ideas or similar manners in it? Yes, we are actually. We can create an NSArray category and iterate each element of it to achieve an identical result. In this article, I'll demonstrate how to create block based functions for map, filter, reduce and flatMap in Objective-C.
First, let’s take a look of how it performs in Swift playground
If you pay attention to the snippet below, you will notice the intent of codes itself is focus on function for each element in a collection and there is no signs of a for-loop pattern. It helps developers to focus on more operations itself but not control flow with data process.
// Map: Iterate a collection and apply the same block operation to each element in it.
print(characters.map({ String($0).uppercased() }))
// Filter: Iterate a collection and return elements that meet a condition.
print(characters.filter({ $0 == "o"}))
// Reduce: Combine all elements in a collection to create a single output.
print(characters.reduce("", { String($0) + String($1) }))
// FlatMap: Flatten a collection of collections.
let _characters = ["Hello".characters, ", ".characters, "playground".characters]
print(_characters.flatMap({ $0 }))Okay, now let’s get hands dirty
Unlike Swift, higher order functions are not built-in natively in Objective-C. Therefore, we need to create our own category to have these functions be accessible. If you browse the codes below, the core concept is not complicated basically. It's all around the function enumerateObjectsUsingBlock: and adds some minor variation according to its purpose.
- (NSArray *)map:(id (^)(id obj))block {
NSMutableArray *mutableArray = [NSMutableArray new];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[mutableArray addObject:block(obj)];
}];
return [mutableArray copy];
}
- (NSArray *)filter:(BOOL (^)(id obj))block {
NSMutableArray *mutableArray = [NSMutableArray new];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if (block(obj) == YES) {
[mutableArray addObject:obj];
}
}];
return [mutableArray copy];
}
- (id)reduce:(id)initial
block:(id (^)(id obj1, id obj2))block {
__block id obj = initial;
[self enumerateObjectsUsingBlock:^(id _obj, NSUInteger idx, BOOL *stop) {
obj = block(obj, _obj);
}];
return obj;
}
- (NSArray *)flatMap:(id (^)(id obj))block {
NSMutableArray *mutableArray = [NSMutableArray new];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
id _obj = block(obj);
if ([_obj isKindOfClass:[NSArray class]]) {
NSArray *_array = [_obj flatMap:block];
[mutableArray addObjectsFromArray:_array];
return;
}
[mutableArray addObject:_obj];
}];
return [mutableArray copy];
}So, how to use them?
The usage is very similar to the ways in Swift but little wordy indeed :) However, the patterns and ideas are still the same. We only take care operations and no need to deal with flow controls.
NSArray *array = @[
@"H",
@"e",
@"l",
@"l",
@"o",
@",",
@" ",
@"w",
@"o",
@"r",
@"l",
@"d",
@"!"
];
// Map: Iterate an array and apply the same block operation to each element in it.
NSLog(@"%@", [array map:^id(id obj) { return [(NSString *)obj uppercaseString]; }]);
// Filter: Iterate an array and return elements that meet a condition.
NSLog(@"%@", [array filter:^BOOL(id obj) { return [(NSString *)obj isEqualToString:@"o"]; }]);
// Reduce: Combine all elements in an array to create a single output.
NSLog(@"%@", [array reduce:@"Hey, " block:^id(id obj1, id obj2) { return [NSString stringWithFormat:@"%@%@", obj1, obj2]; }]);
array = @[
@[@"H", @"e", @"l", @"l", @"o"],
@[@",", @" "],
@[@"w", @"o", @"r", @"l", @"d", @"!"]
];
// FlatMap: Flatten an array of arrays.
NSLog(@"%@", [array flatMap:^id(id obj) { return obj; }]);But…. Wait! there might be something wrong here!!
Yes, the devil is always in the details. That’s why BUT is so important all the time. As a developer with years experience, you might wonder if is it a good practice in Objective-C? The reason is that type safety becomes developers’ responsibility more or less. After all, Swift and Objective-C don’t share the same philosophy at type inference and type safety in many ways. My suggestion is to add a class restrictor for each function to improve safety. Although adding a class restrictor might become wordier in writing, I still think the safer the better in the long run.
The snippet below is to demonstrate the usage with a class restrictor. The full implementation is available at GitHub here.
NSArray *array = @[
@"H",
@"e",
@"l",
@"l",
@"o",
@",",
@" ",
@"w",
@3,
@"o",
@"r",
@"l",
@"d",
@"!"
];
// Map: Iterate an array and apply the same block operation to each element in it.
NSLog(@"%@", [array map:^id(id obj) { return [(NSString *)obj uppercaseString]; } class:[NSString class]]);
// Filter: Iterate an array and return elements that meet a condition.
NSLog(@"%@", [array filter:^BOOL(id obj) { return [(NSString *)obj isEqualToString:@"o"]; } class:[NSString class]]);
// Reduce: Combine all elements in an array to create a single output.
NSLog(@"%@", [array reduce:@"Hey, " block:^id(id obj1, id obj2) { return [NSString stringWithFormat:@"%@%@", obj1, obj2]; } class:[NSString class]]);
array = @[
@[@"H", @"e", @"l", @"l", @"o"],
@[@",", @" ", @3],
@[@6, @8, @6],
@[@"w", @"o", @"r", @"l", @"d", @"!"]
];
// FlatMap: Flatten an array of arrays.
NSLog(@"%@", [array flatMap:^id(id obj) { return obj; } class:[NSString class]]);