Higher Order Functions in Objective-C
No, you did not get fooled by your eyes. The title is not a typo.
First, let's take a look at 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 are 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 applies 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; } - (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; } - (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; }
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 applies 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 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 applies 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]]);














