NSString Cons

One of the cool tricks I saw done with Objective-C a while ago was implementing a concatenation operator on NSString, using : similar to Haskell and ML. I have been unable to find the original site, so I decided to reimplement it myself recently.

One of the cool tricks I saw done with Objective-C a while ago was implementing a concatenation operator on NSString, using : similar to Haskell and ML. I have been unable to find the original site, so I decided to reimplement it myself recently.

A quick caveat to the following: this uses Objective-C trickery that may not be very safe in a production environment. Also, the upcoming Automatic Reference Counting in Clang/LLVM 3.0 will not compile this code. With that out of the way, lets look at how we're going to do this. In Objective-C, methods are backed by a concrete implementation in the form of a standard C function. The runtime uses the IMP type for this, which is defined as:

id (*IMP)(id self, SEL selector, ...)

The ... in this case stands for additional arguments for the method. For example, [@"foo" stringByAppendingString:@"bar"] will eventually be executed by a function matching the prototype below.

id functionName(id self, SEL sel, NSString *otherString)

Additional arguments are just tacked onto the end. This can be combined with standard C varargs. The only caveat is we need to signal the end of the varargs somehow. I used nil.

id stringCons(id self, SEL selector, ...){
	va_list strings;
	NSMutableString *fullString = [[NSMutableString alloc] initWithString:self];
	va_start(strings, selector);
	id currentString = nil;
	// End on nil
	while((currentString = va_arg(strings, id))){
		[fullString appendString:currentString];
	}
	va_end(strings);
	return fullString;
}

There are two approaches to adding a method to a class at runtime in Objective-C. The normal way is to use class_addMethod. This is enough in most cases, but because we have a variable length selector, we can't use this method. Another way methods can be added is with the dynamic method resolution available through NSObject (Mike Ash touches on this method in a post on message forwarding).

+(BOOL)resolveInstanceMethod:(SEL)sel{
	// Check that the selector is just ':' characters
	const char *checkName = sel_getName(sel);
	BOOL isCons = YES;
	int i = 0;
	char c = checkName[i];
	while(c != '\0'){
		if(c != ':'){
			isCons = NO;
			break;
		}
		c = checkName[++i];
	}
	// Add the method, or pass this message up the chain
	if(isCons){
		// Make the type string
		size_t typesSize = 4 + i;
		char *types = malloc(sizeof(char) * typesSize);
		types[0] = '@';
		types[1] = '@';
		types[2] = ':';
		for(int j = 3; j < typesSize - 1; ++j){
			types[j] = '@';
		}
		types[typesSize - 1] = '\0';
		// Add the method
		class_addMethod([self class], sel, stringCons, types);
		return YES;
	}else{
		return [super resolveInstanceMethod:sel];
	}
}

Here all were doing is checking to see if the selector is completely made up of : characters. If it is, we add the stringCons IMP from above for that selector. Note the else block at the end. The call to [super resolveInstanceMethod:sel] is very important, as other parts of a class may depend on this dynamic method resolution.

The final hurdle is how to add +resolveInstanceMethod: to NSString. The safest way would be to subclass NSString, but since NSString is a class cluster this is not possible. I ended up using a category, but a safer choice might be to use method swizzling to insert our method in addition to any NSString may have defined.

That's it to adding a cons-like operator to NSString in Objective-C. To use it, just insert it between instances of NSString ending with nil, like below.

NSString *one = @"1";
NSString *two = @"2";
NSString *three = @"3";
NSString *all = [one:two:three:nil];