My new project: Tact, a simple chat app.

A gotcha about Objective-C categories in the same file as the main implementation

November 08, 2013

I ran into some weird errors today that made me scratch my head for half an hour until I reasoned through it with my colleagues.

Suppose you have the following implementation file:

@implementation SomeClass

- (void)someMethod
{
	[self someCategoryMethod];
}

@end

@implementation SomeClass (SomeCategory)

- (void)someCategoryMethod
{
	[self doSomething];
}

@end

If you built the above, you’d get an error:

No visible @interface for 'SomeClass' declares the selector 'someCategoryMethod'

What gives? I am not a compiler expert and cannot explain it in 100% correct technical terms, but turns out that in this case, top-to-bottom parsing rules apply. The source file is parsed and compiled top-to-bottom. By the time you are trying to call the method, the compiler has not yet seen the method declaration, so it complains.

The fix, thus, is to put the interface ahead of the method call:

@interface SomeClass (SomeCategory)

- (void)someCategoryMethod;

@end

@implementation SomeClass

- (void)someMethod
{
	[self someCategoryMethod];
}

@end

@implementation SomeClass (SomeCategory)

- (void)someCategoryMethod
{
	[self doSomething];
}

@end

This works fine. Alternatively, you can just put the category implementation ahead of the main implementation, and that works as well.

Why do I feel it’s worth pointing out? Because it was counter-intuitive to me. The implementation that started this post did not compile, but felt perfectly intuitive for me. It felt intuitive because with recent Objective-C language advances, Apple has trained us that it is not necessary to declare methods within the same implementation file. So, this file would compile perfectly well:

@implementation SomeClass

- (void)someMethod
{
	[self someOtherMethod];
}

- (void)someOtherMethod
{
	[self doSomething];
}
@end

There is no need to declare someOtherMethod ahead of the first time that it is called. I was expecting the same paradigm to extend to category implementations within the same file, but apparently, that is not the case. In case of categories, you need to have the interface/declaration appear before you call it.