Marcel Voss

Higher Order Functions in Objective C

by Marcel on December 26, 2019

What do all higher order functions in Swift have in common? They abstract boilerplate code away by hiding it in their highly generic implementations, focussing on what to achieve rather than on how to get there because that’s what we usually care about. By abstracting boilerplate away, we can eliminate plenty of failures we could potentially introduce by writing the same thing over and over again, not realizing a small mistake.

What are higher order functions, anyway? Wikipedia has a quite lengthy explanation:

In mathematics and computer science, a higher-order function is a function that does at least one of the following:

  • takes one or more functions as arguments (i.e. procedural parameters),
  • returns a function as its result.

Simply put, they are functions that operate on other functions by either taking a function as an argument, or returning a function.

In case you have ever been exposed to functional programming, this will sound very familiar to you and, they indeed reveal that Swift is influenced by functional programming (yay) - unlike Objective-C which is famously known for verbosity.

Although, Objective-C is definitely not functional, we can easily add our own higher order functions to our applications that will behave similarily to their Swift counterparts, keeping a consistent and predictable behavior across languages. When using HOFs for the first time, they might seem quite magical but once you have taken a look at their implementations they will seem pretty trivial to you - and with Swift we can actually do that.

map

So, what does the map function in Swift actually do? Broadly simplified, it does nothing else than iterating over every item in a collection, applying a closure to each item and returning a new array containing all transformed items. No compiler magic, no tricks, nothing fancy.

func map<T>(_ transform: (Element) -> T) -> [T] {
    var results = [T]()
    for element in self {
        results.append(transform(element))
    }
    return results
}

To be fair, this (and all the other code samples for the HOFs in Swift) is a simplified version of the real implementation for map(_:) with the main difference being the lack of throwing and rethrowing of errors.

Looking at the implementation of map(_:) in the standard library, it becomes apparent how trivial it is. In fact, we can do the same thing in Objective-C quite easily. So, why not taking this concept and appyling it to our Objective-C code? We can achieve the same result in Objective-C with very few lines of code:

- (NSArray *)map:(NS_NOESCAPE id  _Nonnull (^)(id _Nonnull))transform {
    NSParameterAssert(transform);

    NSMutableArray *array = [NSMutableArray arrayWithCapacity:self.count];
    for (id obj in self) {
        [array addObject:transform(obj)];
    }
    return [array copy];
}

It’s obviously a bit more verbose than its Swift equivalent but does exactly the same thing in a similar fashion - making our code a bit more elegant by taking away verbosity (and repetition) at call site.

In Swift, map(_:) is defined within an extension on the Sequence protocol, making it possible to have a single implementation that is being used by dictionaries, sets, arrays and essentially every other type that conforms to Sequence, making heavy use of generics and allowing the code to be easily reused by multiple collection types. As NSArray, NSDictionary and NSSet are concrete types and Objective-C only has categories, we will have to add slightly tweaked implementations to categories for each collection type we want to have a map function, reducing the amount of reusability unfortunately.

compactMap

Next up: compactMap. compactMap is one of my favorite things in Swift. In Swift, it iterates over a collection, applies a transformation but only returns the non-nil results from a transformation, basically doing the same thing as map but stripping all nil elements:s

func compactMap<T>(_ transform: (Element) -> T?) -> [T] {
    var results = [T]()
    for element in self {
        if let transformed = transform(element) {
            results.append(transformed)
        }
    }
    return results
}

Although, Objective-C doesn’t have a notion of optionality (everything may or may not be nil), we can add a compactMap function. In Objective-C, arrays for example can not contain nil elements (only using the [NSNull null] singleton), but we can use the compactMap function in Objective-C to make sure, we’re not crashing our app by attempting to insert nil elements resulting from a transform to the array. Actually not only adding syntactical sugar here and there but effectively improving the robustness of our app and again, keeping our call site quite clean.

- (NSArray *)compactMap:(NS_NOESCAPE id _Nullable (^)(id _Nonnull))transform  {
    NSParameterAssert(transform);

    NSMutableArray *array = [NSMutableArray new];
    for (id element in self) {
        id transformedElement = transform(element);
        if (transformedElement) {
            [array addObject:transformedElement];
        }
    }
    return [array copy];
}

filter

filter does basically what you would expect: it filters a collection using a certain predicate and only returns matching elements - quite useful.

func filter(_ isIncluded: (Element) -> Bool) -> [Element] {
    var results = [Element]()
    for element in self {
        if isIncluded(element) {
            results.append(element)
        }
    }
    return results
}

Although, the Objective-C code mentions a predicate, this is completely unrelated to the NSPredicate class as our predicate is simply some code that should return some BOOL. We could have went with an NSPredicate and a more powerful solution as well but I decided to better keep the Objective-C implementation as close to the Swift equivalent as possible, in order to keep it predictable and familiar. You could definitely build your own version using NSPredicate.

- (NSArray *)filter:(NS_NOESCAPE BOOL (^)(id _Nonnull))predicate {
    NSParameterAssert(predicate);

    NSMutableArray *array = [NSMutableArray new];
    for (id element in self) {
        if (predicate(element)) {
            [array addObject:element];
        }
    }
    return [array copy];
}

forEach

forEach(_:) is very similar to map(_:) but differs in that it doesn’t have a transformation closure (but has a body closure) that is being applied to every element in a collection - it is more or less only syntactic sugar for iterating over a collection and performing side effects for each element in the collection.

It is in fact highly discouraged to use forEach(_:) for transforming elements as someone who is reading through your code, will expect something completely different when they see a forEach(_:).

Nevertheless, forEach(_:) can be super useful and has the same benefits other HOFs have: focussing on the relevant code, abstracting away the for-in loop.

func forEach(_ body: (Element) -> Void) {
    for element in self {
        body(element)
    }
}

From all of the HOFs shown here, forEach(_:) is probably the one that can be represented in Objective-C the closest to the one in Swift.

- (void)forEach:(NS_NOESCAPE id _Nonnull (^)(id _Nonnull))block {
    NSParameterAssert(block);

    for (id obj in self) {
        block(obj);
    }
}

What about type (un)safety?

You might have noticed that the Objective-C code above uses id for all elements in a sequence. Unlike we do in Swift, we don’t have any guarantees about type safety and need to be extra careful (but that should be true for all of our code anyway).

Although, not having any guarantees, we can still improve our code by making use of Objective-C’s lightweight generics in our interfaces for helping the compiler better understand what we want to achieve (like we do with nullability annotations) and getting better code completion suggestions.

For example, the interface for our category on NSArray that adds the higher order functions, might look something like this:

@interface NSArray<__covariant ObjectType> (HigherOrderFunctions)
- (NSArray *)map:(NS_NOESCAPE id (^)(ObjectType))transform NS_SWIFT_UNAVAILABLE("Use Swift's native map(_:) function instead.");
@end

The __covariant keyword is being used to indicate that subtypes are acceptable, while ObjectType is the name of the placeholder that will be replaced by the actual type. This is the same trick Apple uses for allowing e.g. NSArray to operate using generics, as you can see in its header:

@interface NSArray<__covariant ObjectType> : NSObject <NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration>

Unfortunately, it is not possible to refer to placeholder names within the implementation. Therefore, instead of referring to ObjectType we will still need to use id. That ultimately means we’re still not getting any additional type safety within our implementation (bummer).

Nevertheless, ObjectType is being replaced at call site with the proper type and the code completion will give us better results. So, overall quite an improvement.

Conclusion

We have seen that we can add higher order functions easily to our Objective-C codebase. For the dinosaurs among us that still have to write Objective-C on a day-to-day base, this can make their life a bit swiftier. For us at SumUp, it did and we use it quite a lot throughout our app(s).

Tl;Dr: it is a beautiful thing Swift’s code is publicly available on GitHub and in case we’re interested in how something is done, we can simply head over to its repository and read through it. Sometimes, this is not only beneficial to our understanding but also gives us an inspiration on how we can do something swifty in Objective-C (or other languages) as well.

There are many, many more higher order functions we could bring from Swift to the Objective-C world but I’ll leave that up to you. Enjoy!