Dependency Injection in Objective-C... sort of
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
-
[Polymorphism and Multimethods]
02-03-2023 -
[Global Day of CodeRetreat - recap]
07-11-2022 -
[House automation tooling - Part 4 - Finalized]
01-11-2022 -
[House automation tooling - Part 3 - London-School and Double-Loop]
02-07-2022 -
[Modern Programming]
14-05-2022 -
[House automation tooling - Part 2 - Getting Serial]
21-03-2022 -
[House automation tooling - Part 1 - CL on MacOSX Tiger]
07-03-2022 -
[Common Lisp - Oldie but goldie]
18-12-2021 -
[Functional Programming in (Common) Lisp]
29-05-2021 -
[Patterns - Builder-make our own]
13-03-2021 -
[Patterns - Builder]
24-02-2021 -
[Patterns - Abstract-Factory]
07-02-2021 -
[Lazy-sequences - part 2]
13-01-2021 -
[Lazy-sequences]
07-01-2021 -
[Thoughts about agile software development]
17-11-2020 -
[Test-driven Web application development with Common Lisp]
04-10-2020 -
[Wicket UI in the cluster - the alternative]
09-07-2020 -
[TDD - Mars Rover Kata Outside-in in Common Lisp]
03-05-2020 -
[MVC Web Application with Elixir]
16-02-2020 -
[Creating a HTML domain language in Elixir with macros]
15-02-2020 -
[TDD - Game of Life in Common Lisp]
01-07-2019 -
[TDD - classicist vs. London Style]
27-06-2019 -
[Wicket UI in the cluster - reflection]
10-05-2019 -
[Wicket UI in the Cluster - know how and lessons learned]
29-04-2019 -
[TDD - Mars Rover Kata classicist in Scala]
23-04-2019 -
[Burning your own Amiga ROMs (EPROMs)]
26-01-2019 -
[TDD - Game of Life in Clojure and Emacs]
05-01-2019 -
[TDD - Outside-in with Wicket and Scala-part 2]
24-12-2018 -
[TDD - Outside-in with Wicket and Scala-part 1]
04-12-2018 -
[Floating Point library in m68k Assembler on Amiga]
09-08-2018 -
[Cloning Compact Flash (CF) card for Amiga]
25-12-2017 -
[Writing tests is not the same as writing tests]
08-12-2017 -
[Dependency Injection in Objective-C... sort of]
20-01-2011