[blog] | [projects] | [about] | [imprint]

Dependency Injection in Objective-C... sort of

20 January 2011
 

The post will be about Dependency Injection (DI) in Objective-C. DI or the pattern behind it IoC (Inversion of Control) is well known in the Java world. There are quite a few frameworks available like Spring, EJB, Guice, etc.
On Mac I didn't find something like it. So I've implemented a proof of concept.

The goal was to inject a service class instance into another object, let’s say a consumer of that service. Also it should be possible that the service object can be mocked and a different instance of the service be injected for unit testing.

Let’s see, what we need first is some kind of registration facility where we can register classes by name. When an instance of that class is asked for a new instance will be created and it will be returned. Alternatively an instance of a class can be set for a name, then this instance will be returned instead. With this we can set mock objects for unit testing while in production the real class is used.

This is the „DependencyRegistration“ facility’s interface:

@interface DependencyRegistration : NSObject {
    NSMutableDictionary *classRegistrations;
    NSMutableDictionary *objectInstances;
}
+ (DependencyRegistration *)registrator;

- (void)addRegistrationForClass:(Class)aClass withRegName:(NSString *)aRegName;
- (void)removeClassRegistrationForRefName:(NSString *)aRegName;
- (void)clearClassRegistrations;

- (void)addObject:(id)anObject forRegName:(NSString *)aRegName;
- (void)clearObjectForRegName:(NSString *)aRegName;
- (void)clearAllObjects;

- (id)objectForRegName:(NSString *)aRegName;
@end


Here are some relevant parts of the implementation:

- (void)addRegistrationForClass:(Class)aClass withRegName:(NSString *)aRegName {
    [classRegistrations setObject:aClass forKey:aRegName];
}

- (void)addObject:(id)anObject forRegName:(NSString *)aRegName {
    [objectInstances setObject:anObject forKey:aRegName];
}

- (id)objectForRegName:(NSString *)aRegName {
    id anObject = [objectInstances objectForKey:aRegName];
    if(!anObject) {
        Class class = [classRegistrations objectForKey:aRegName];
        anObject = [[[class alloc] init] autorelease];
    }
    return anObject;
}


This facility is implemented as singleton.
As you can see a class or an instance of a class can be associated with a registration name. the

-objectForRegName:

method either creates an object from a registered class or uses a class instance if one has been set.

Now how is this going to be of use? Let’s continue. The next thing we need is a service protocol and a service class that implements this protocol:

@protocol MyServiceLocal
- (NSString *)sayHello;
@end


The protocol should be placed outside of the service class implementation, in another header file. Something like „Services.h“.

#import 
@interface MyService : NSObject  {
}
- (NSString *)sayHello;
@end

@implementation MyService
- (id)init {
    return [super init];
}
- (void)finalize {
    [super finalize];
}
- (NSString *)sayHello {
    return @"Hello";
}
@end


I’ve mixed interface and implementation here which normally is separated in .h and .m files.
Good. We have our service.
Now we create a consumer of that service that get’s the service injected.

#import 
@interface MyConsumer : NSObject {
    id myServiceInstance;
}
- (NSString *)letServiceSayHello;
@end

@interface MyConsumer ()
@property (retain, readwrite) id myServiceInstance;
@end

@implementation MyConsumer
@synthesize myServiceInstance;

- (id)init {
    if(self = [super init]) {
        self.myServiceInstance = INJECT(MyServiceRegName);
    }
    return self;
}
- (NSString *)letServiceSayHello {
    NSString *hello = [myServiceInstance sayHello];
    NSLog(@"%@", hello);
    return hello;
}
@end


This is the consumer.
The interesting part is the INJECT(MyServiceRegName). Now where does this come from? The INJECT is just a #define. The MyServiceRegName is also a #define which specifies a common name for a service registration. We can add this to the DependencyRegistration class like this:

#define INJECT(REGNAME)     [[DependencyRegistration registrator] objectForRegName:REGNAME]
#define MyServiceRegName     @"MyService"


In fact all service registration names could be collected in this class but they could also be someplace else.
The INJECT define does nothing else than get an instance of the DependencyRegistration singleton and call the -objectForRegName: method which will either return an instance from a created Class or an already set object instance.

The injection does occur here in an initialisation method.
It could actually also do via a setter or init like:

[consumer setMyService:INJECT(MyServiceRegName)];
[[Consumer alloc] initWithService:INJECT(MyServiceRegName)];


The way this is implemented either every consumer get’s a new instance of the service or all get the same instance depending on whether an instance has been set in the DependencyRegistration object or not.

Now let’s create a unit test to see if it’s working:

#import <SenTestingKit/SenTestingKit.h>
#import 
@interface MyConsumerTest : SenTestCase {
    DependencyRegistration *registrator;
}
@end

@implementation MyConsumerTest
- (void)setUp {
    registrator = [DependencyRegistration registrator];
    [registrator addRegistrationForClass:[MyService class] withRegName:MyServiceRegName];
}

- (void)testSayHello {
    MyConsumer *consumer = [[[MyConsumer alloc] init] autorelease];
    STAssertNotNil(consumer, @"");
    NSString *hello = [consumer letServiceSayHello];
    STAssertEquals(hello, @"Hello", @"");
}
@end


You will see that it works when you execute this test. Here just a class name is registered which means that a new class instance is created and injected to the consumer.

There is plenty of space for improvements of this.
In terms of Java what we have here is either an application scope object (when a service instance has been added via -addObject::) or a request scope object (when no service instance has been added and one is created each time) is passed the the caller.

Well, after all the DependencyRegistration class is not much more than an Abstract Factory for multiple class types.


Cheers

[atom/rss feed]