Generic “functions” in Objective-C

For the past few days I've been trying to bring one of my older codebases up to date with iOS 9 and Xcode 71. Today I stumbled on some code using map method from Mike Ash's MACollectionUtilities library. I started thinking whether it's possible to make this method generic. Let's see what we can come up with.

Generic Method

The best way to tackle the map implementation would be to add a generic type to a method, similarly to how it's done in Swift:

- (NSArray<ReturnType> *)map:(ReturnType (^)(ObjectType obj))block;

This won't compile, though because the compiler won't know that ReturnType is a generic type in the context of this method. Declaring another generic type through category on NSArray:

@interface NSArray <ObjectType, ReturnType> (AHKMap)

- (NSArray<ReturnType> *)map:(ReturnType (^)(ObjectType obj))block;

@end

is also not possible. It doesn't make much sense anyway, as we won't be able to use one instance of NSArray to map its objects to two different types.

Wrapping Method in a New Class

Since the only way to declare generic parameters and return values is through a class interface, we'll wrap our map method in a new class:

@interface AHKArrayMapper <InputType, OutputType> : NSObject

- (NSArray<OutputType> *)map:(NSArray<InputType> *)inputArray withBlock:(OutputType (^)(InputType obj))block;

@end

@implementation AHKArrayMapper

- (nonnull NSArray *)map:(nonnull NSArray *)inputArray withBlock:(nonnull id  _Nonnull (^)(id _Nonnull))block
{
  NSMutableArray *array = [NSMutableArray new];
  for (id obj in inputArray) {
    [array addObject:block(obj)];
  }

  return [array copy];
}

@end

AHKArrayMapper's only method takes an array of InputType objects and a block mapping from InputType to OutputType. It returns an array of OutputType objects. The implementation doesn't really differ from the one without generics, because generics aren't available in implementation files:

So I've concluded that Objective-C Generics simply don't make the placeholders available to implementations. Unfortunately I cannot confirm against Apples code.

It's easy to see that we could implement other generic functions (well, methods but they won't be referencing self) in a similar way. We'd end up with lots of small classes, though.

In Action

Let's say we have an array of NSNumber instances:

NSArray <NSNumber *> *input = @[@0, @1, @2];

that we want to transform to an array of strings:

@[@"0", @"1", @"2"];

First, we have to instantiate a mapper and declare what types it's going to operate on:

AHKArrayMapper<NSNumber *, NSString *> *mapper = [AHKArrayMapper new];

Then, we can use it as shown below:

NSArray<NSString *> *output;
output = [mapper map:input withBlock:^NSString *(NSNumber *obj) {
  return [obj stringValue];
}];

The cool thing is that when we finish typing [mapper map, Xcode will correctly complete the method call along with all types:

Code Completion

Conclusion

Lightweight generics are one of the better improvements in Objective-C in the past few years. Even though they only operate at compile-time they're able to bring a little more safety to Objective-C.

It was possible to create a generic map for NSArray. However, because we had to design it in the way that fits generics implementation, its API isn't the most obvious one. If you plan to use generics extensively in your projects it's best to move to Swift, as its support for generics is fully built into the language itself.


  1. One could argue that I still have technical debt because I don't use Swift there.