Startseite > Programming > Objective-C Dependency Injection… sort of

Objective-C Dependency Injection… sort of


Well, this is my first blog post, ever. I’m not yet very familiar with this but I’ll try.
Forgive me my poor english at times. It is not my native language and sometime it might sound strange what I am telling.

The first post will be about the programming pattern of Dependency Injection that is well known in the Java world. There are quite a few frameworks available like Spring, EJB, Guice, etc.
I’ve come to learn about those frameworks and since I am a Mac and Cocoa liker I was wondering whether there is something similar available on the Mac platform for the Objective-C programming language.
My searches were successful in that I found that there is not currently something with this functionality. So I tried to write something myself. This is more or less just a proof of concept but it might serve the purpose.

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, first of all what is needed is some kind of registration facility that let’s us register classes for a particular registration name. When an instance of that class is needed it will be created and returned. Alternatively an instance of a class can be set for a registration name, then this instance will be returned instead. That let’s us put a mock object 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 implementation details:

- (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;
}

The other methods are just removing either one entry or all entries from the dictionaries.
This facility is implemented as singleton, a very common pattern in Cocoa.
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? Very well, let’s continue. The next things is we need 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 <Services.h>
@interface MyService : NSObject <MyServiceLocal> {
}
- (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 <Services.h>
@interface MyConsumer : NSObject {
    id<MyServiceLocal> myServiceInstance;
}
- (NSString *)letServiceSayHello;
@end

@interface MyConsumer ()
@property (retain, readwrite) id<MyServiceLocal> 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. Easy, 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 collection 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 <DependencyRegistration.h>
@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.
With a little of logic to the DependencyRegistration with some lifecycle stuff and synchronisation it would be possible to have something like stateless session objects, too.

I guess I’ll start implementing this soon, let’s see how it goes…
That’s it for now.

About these ads
  1. Februar 13, 2013 um 11:58 vormittags

    “Objective-C Dependency Injection sort of Manfred’s Blog” honestly causes me personally think a little bit further. I appreciated each and every single section of it. Many thanks -Cornell

  1. No trackbacks yet.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ photo

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

Folgen

Erhalte jeden neuen Beitrag in deinen Posteingang.

%d Bloggern gefällt das: