Serious Objective-C

There are some challenges to making large and/or complex software with Objective-C. The main thing I miss from other systems is levels of method protection.

However, it is quite easy to get the effects of traditional public/package/private method protection levels with a bit of work. First, and obviously, public methods go into the main header for the class:

#import <Foundation/Foundation.h>
@interface Counter
{
  NSNumber *number;
}
-(NSNumber *)number;
-(void)increment;
@end

And private methods exist only in the implementation of the class:

#import "Counter.h"

@implementation Counter
-(NSNumber *)number
{
  return number;
}

-(void)setNumber:(NSNumber *)num
{
  number = [num copy];
}

-(void)increment
{
  [self setNumber:[NSNumber numberWithInteger:[number integerValue] + 1]];
}
@end

Of course if you do this you have to put the implementation of -setNumber: above the implementation of -increment to avoid a compiler warning about an unknown selector. We can avoid this by putting a class extension that declares the method in the implementation file:

#import "Counter.h"

@interface Counter ()
-(void)setNumber:(NSNumber *)num;
@end

@implementation Counter
-(NSNumber *)number
{
  return number;
}

-(void)increment
{
  [self setNumber:[NSNumber numberWithInteger:[number integerValue] + 1]];
}

-(void)setNumber:(NSNumber *)num
{
  number = [num copy];
}
@end

Usefully we can use class extensions to override the definition of properties so long as we extend the definition in a compatible way. Broadly this means that we can publicly declare a property as readonly and then re-declare it as readwrite in a class extension. We can then exploit the @synthesize facility to throw out a load of code. If we revise the above example in this way the header becomes:

#import <Foundation/Foundation.h>

@interface Counter
@property(readonly,copy,nonatomic)NSNumber *number;
-(void)increment;
@end

And the main implementation becomes:

#import "Counter.h"

@interface Counter ()
@property(readwrite,copy,nonatomic)NSNumber *number;
@end

@implementation Counter
@synthesize number;

-(void)increment
{
  self.number = [NSNumber numberWithInteger:number.integerValue + 1];
}
@end

But what do we do if the -increment method should not be available outside the package of code we are developing? Obviously it must be removed from the @interface, but we still need to let code within our package know that it exists, and we want the compiler to make sure it gets implemented. To help with this we declare it in a class extension in another header file and limit that file’s availability to the package we’re developing (if making a Framework in Xcode, set it to ‘Project’ while the main headers remain ‘Public’). The public header is therefore:

#import <Foundation/Foundation.h>

@interface Counter
@property(readonly,copy,nonatomic)NSNumber *number;
@end

The package header is:

#import "Counter.h"

@interface Counter ()
-(void)increment;
@end

And the main implementation becomes:

#import "Counter_package.h"

@interface Counter ()
@property(readwrite,copy,nonatomic)NSNumber *number;
@end

@implementation Counter
@synthesize number;

-(void)increment
{
  self.number = [NSNumber numberWithInteger:number.integerValue + 1];
}
@end

That gives a very useful compile-time approximation of public-, private- and package-scoped methods. It is also possible to create a poor approximation of Java’s protected-scoped methods using the same techniques: simply create another header containing a class-extension and document that only implementors of subclasses may include it. This is obviously flawed because no-one reads documentation let-alone does what it says.