Nil messaging in Objective-C has a fairly well document behavior. In brief, messaging nil has not effect and the return value from a message sent to nil is discussed in the Programming with Objective-C

If you expect a return value from a message sent to nil, the return value will be nil for object return types, 0 for numeric types, and NO for BOOL types.

This is a very handy feature of Objective-C that might seem at first unfamiliar to Ruby programmers. In fact, invoking a method on nil in Ruby gives a NoMethodError as following:

NoMethodError: undefined method `length' for nil:NilClass

It’s a very different behavior from Objective-C that many Ruby programmers have come to rely on. I myself prefer the way nil is handled in Objective-C but let’s assume for a second that we wanted to achieve the Ruby behavior in Objective-C. Needless to say that it is probably a very bad idea but since it could be fun, let’s have a look at the ObjC runtime and give it a try!

We first need to find which part of the runtime is in charge of handling messages to nil. As described very nicely in this post, a message to nil is handled directly in objc_msgSend.

Given that the ObjC runtime is open-source, we can have a look at the objc_msgSend implementation for x86_64. It is assembly but, trust me, it is fairly readable.

Our first step is to find the entry point of objc_msgSend that looks as following:

ENTRY	_objc_msgSend
DW_START _objc_msgSend

NilTest	NORMAL

GetIsaFast NORMAL		// r11 = self->isa
CacheLookup NORMAL, _objc_msgSend  // r11=method, eq set (nonstret fwd)
jmp	*method_imp(%r11)	// goto *imp
...

We can quickly notice that one of the first instruction is a NilTest macro that itself looks as following:

.macro NilTest
.if $0 != STRET
	testq	%a1, %a1
.else
	testq	%a2, %a2
.endif
	jz	LNilTestSlow_f
LNilTestDone:
.endmacro

.macro NilTestSupport
	.align 3
LNilTestSlow:
.if $0 != STRET
	movq	__objc_nilReceiver(%rip), %a1
	testq	%a1, %a1	// if (receiver != nil)
.else
	movq	__objc_nilReceiver(%rip), %a2
	testq	%a2, %a2	// if (receiver != nil)
.endif
	jne	LNilTestDone_b	//   send to new receiver

.if $0 == FPRET
	fldz
.elseif $0 == FP2RET
	fldz
	fldz
.endif
.if $0 != STRET
	xorl	%eax, %eax
	xorl	%edx, %edx
	xorps	%xmm0, %xmm0
	xorps	%xmm1, %xmm1
.endif
	ret
.endmacro

After a few tests for the return type (struct and floating-point need special handling), we can notice that a nil receiver, if set (not nil) is given a chance to act as the message receiver. Otherwise, a few registers usually holding return values are cleant and the function returns.

The __objc_nilReceiver is not usually set but if we found a way to set it to an object that we create we could alter the behavior of nil messaging!

Luckily, objc-private.h declares the following function:

extern id _objc_setNilReceiver(id newNilReceiver);

That’s it, if we call this function with our custom object we will able to intercept any message to nil.

Our NilCatcher class will only need to implement two methods methodSignatureForSelector: and forwardInvocation:. Since our implementation of forwardInvocation: won’t actually need a valid NSMethodSignature we will return the method signature of a basic method on NSObject in methodSignatureForSelector:. Eventually, we will throw an exception in forwardInvocation:, logging the selector.

Instead of addign a new class in our project we will create the class at runtime and provide a couple of method implementations with blocks, just because it quicker and more fun. The code is shown below but also available as a gist which should be slightly easier to read.

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

extern id _objc_setNilReceiver(id newNilReceiver);

static id _createNilCatcherObject(void)
{
	Class NilCatcher = objc_allocateClassPair([NSObject class], "NilCatcher", 0);

	NSMethodSignature * (^methodSignatureForSelectorBlock)(id, SEL) = ^ NSMethodSignature * (id _block, SEL selector) {
		/*
			We will not actually use the method signature in forwardInvocation so any signature will do it.
		 */
		return [NSObject instanceMethodSignatureForSelector:@selector(description)];
	};
	IMP methodSignatureForSelectorIMP = imp_implementationWithBlock(methodSignatureForSelectorBlock);
	Method methodSignatureForSelectorMethod = class_getClassMethod([NSObject class], @selector(methodSignatureForSelector:));
	class_addMethod(NilCatcher, @selector(methodSignatureForSelector:), methodSignatureForSelectorIMP, method_getTypeEncoding(methodSignatureForSelectorMethod));

	void (^forwardInvocationBlock)(id, NSInvocation *) = ^ void (id _block, NSInvocation * invocation) {
		@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"Attempting to message %s to nil", sel_getName([invocation selector])] userInfo:nil];
	};
	IMP forwardInvocationIMP = imp_implementationWithBlock(forwardInvocationBlock);
	Method forwardInvocationMethod = class_getClassMethod([NSObject class], @selector(forwardInvocation:));
	class_addMethod(NilCatcher, @selector(forwardInvocation:), forwardInvocationIMP, method_getTypeEncoding(forwardInvocationMethod));

	return [NilCatcher new];
}

int main(int argc, const char **argv)
{
	@autoreleasepool {
		id nilCatcher = _createNilCatcherObject();
		_objc_setNilReceiver(nilCatcher);

		[(id)nil isEqualToString:@"Cat"];
	}
	return 0;
}

And that’s it! If you build and run you should crash on an NSInternalInconsistencyException when attempting to messaging nil, which should make any Rubyist feel at home! ;)

I cannot stress enough on the fact that you should probably never even think of using this. The Cocoa frameworks surely rely heavily on nil messaging being allowed and having no effect.

That said, it was a fun experiment and I hope you learnt something new.