Alchemic - DI with "Style!"

framework   alchemic   di   objective-c  

Ok, I'm going to be blowing my own trumpet here a bit so hang on :-)

A while ago I did a brown bag at work covering 3 of the most significant DI frameworks I could find for iOS. In researching, experimenting and trying them out, I decided that whilst each had their merits, none of them really did what I wanted. To be fair though, my previous DI framework experience was with the Spring framework so matching that is a very big ask. But in each case I rapidly found things that effectively prevented me from using them.

So I decided to write my own!

The criteria:

  • Must be easy to use, so macro driven because the frameworks I tried that (snobbishly) refused to use macros ended up requiring a lot of code.

  • Support all the basic methods of creating objects - Straight init, initialisers with args, factory methods, etc.

  • Auto-start registered singletons.

  • Automatically detect and inject the UIApplicationDelegate instance.

  • Bring in some of the more useful features from Spring's DI. Search by name, automatic array population, etc.

  • It must simplify the resulting code. In other words, if the framework is not making your life easier, it's not worth using.

There are a lot of other goals as well, but these were the main ones.

Alchemic

Now after many months of experimenting, refactoring and writing code, I have a new framework: Alchemic. I derived the name from the word Alchemy because I liked the concept of a DI framework creating and assembling things for you to use, empowering you to get things done.

Installing

First things first, Alchemic is targeted at iOS8+. You can include it in your project in a number of ways, but my preferred and recommended technique is to make use of Carthage because it brings all your dependencies in as frameworks, making life much easier. Alchemic has one direct dependency, and that is to Story Teller which is a logging framework I built specifically to support the highly technical requirements of debugging a DI framework.

So assuming you are using Carthage, add this to your Cartfile

github "drekka/Alchemic" "master"

Build the dependencies and drag the resulting Alchemic.framework into your project.

Using Alchemic

I have three main documents in the Alchemic repo that you will find useful in understand Alchemic and how to use it:

Lets start with singletons. We all know singletons are evil, but in many cases a necessary evil. One of the core uses of DI frameworks is to manage the objects in your project that are singletons and to inject them where you need them. This removes the need for singleton startup code and for class accessor methods to access them. Lets look at an example first with typical singleton code added:

Server.h

@interface Server : NSObject
+(Server *) shareServer;
-(NSString *) getData;
@end

Server.m

@implementation Server
+(Server *) shareServer {
    static Server *_internalServerRef;
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        _internalServerRef = [[self alloc] init];
    });
    return _internalServerRef;
}
-(NSString *) getData {
     // Go get some data
     return theData;
}   
@end

I won't go into why this is a pain. It just is. Now here's the same class registered with Alchemic (still as a singleton):

Server.h

@interface Server : NSObject
-(NSString *) getData;
@end

Server.m

#import <Alchemic/Alchemic.h>
@implementation Server
AcRegister()  
-(NSString *) getData {
     // Go get some data
     return theData;
}   
@end

Now that better. Plus now being managed by Alchemic, it's much easier to test as well. Now how do we access it? Again, old code:

AnotherClass.m

@implementation AnotherClass
-(void) someMethod {
    NSString *data = [[Server sharedServer] getData]; 
}
@end

New code:

AnotherClass.m

@implementation AnotherClass {
    Server *_server;
}
AcInject(_server)  
-(void) someMethod {
    NSString *data = [_server getData]; 
}
@end

This time the singleton code has the advantage in terms of shortness. But as we know, it's almost impossible to test. A good DI framework changes this and that is exactly what Alchemic does because instead of the code "Reaching out" to get a Server, the framework is injecting it.

Still, I'm not really selling this at this stage. So it does what singletons can do, so what?

Ok, here's the next thing. What about creating a singleton using a custom initializer? here's Alchemic's take on this:

Server.m

#import <Alchemic/Alchemic.h>
@implementation Server
AcInitializer(initWithConnection:, AcArg(Connection, AcClass(Connection)))  
-(instancetype) initWithConnection:(Connection *) conn {
    self = [super init];
    // finishing initing
     return self;
}   
@end

Notice the AcArg(...) macro. What it does is to look up Alchemic's list of objects and locate one that's a Connetction class, then pass that to the initializer. But not just that, Alchemic also makes sure that if the Connection instance needs any injected values, that it finds and injects those first. And if those need injections, that they are handled and so on.

That's pretty cool. Especially as you don't need to write any code to get all of it. But what about if I want to create a number of objects with different setups? Check this out:

@implementation Connection

AcMethod(Connection, createConnectionWithURL:,  
    AcWithName(@"db"), AcArg(NSURL, AcValue([NSURL urlWith...])));
AcMethod(Connection, createConnectionWithURL:,  
    AcWithName(@"server"), AcArg(NSURL, AcValue([NSURL urlWith...])));

-(Connection *) createConnectionWithURL:(NSURL *) url {
    // Create ...
    return connection;
@end

Now Alchemic will create two connections, one called "db" which points to the database and one called "server" which points to the server. And if we want to inject them into any class:

@implementation AnotherClass {
    Connection *_dbConnection;
}
AcInject(_dbConnection, AcName(@"db"))  
@end

Here we tell Alchemic that we need the _connection variable injected, and which instance we want. It does the rest.

But wait - there's more ...

Actually a lot more. But I'm not going to go into the details here. Hopefully this will have given you an idea of what Alchemic can do and how it does it. Check out the doco to really get an idea of what it can do.


Comments powered by Disqus