Keep your hats on! - Typhoon - The Dependency Injection Framework

Keep your hats on! - Typhoon - The Dependency Injection Framework

What is the Dependency Injection (DI)? When I heard about DI for the first time it sounded very difficult to understand. Luckily, I was wrong ;).

Dependency injection is a simple principle. To summarize it in on sentence „The object should rely on it’s dependencies, but it shouldn’t instantiate them.”.


How? – The example

In my life I’ve learned that nothing describes something as good as a good example. Assume you have a LoginSystem. Its duty is to receive a User object and save it for further needs. It uses UserRepository to save User object which came earlier from an API. First I will show you how can it looks like when you don’t follow with DI objectives.

typedef void(^LoginSystemLoginCallback)(BOOL success, NSError *error);

@interface LoginSystem : NSObject
- (void)loginWithRequest:(LoginRequest *)request callback:(LoginSystemLoginCallback)callback;
@end
@implementation LoginSystem

- (void)loginWithRequest:(LoginRequest *)request  callback:(LoginSystemLoginCallback)callback{
    [[DumbAPIConnection new] invokeRESTRequest:request success:^(id response) {
        [self storeUserFromResponse:response];
        [self tryToInvokeCallback:callback error:nil];
    } failure:^(NSError *error) {
        [self tryToInvokeCallback:callback error:error];
    }];
}

- (void)storeUserFromResponse:(id)response {
    User *user = [[DumbUserParser new] parseResponse:response];
    [[UserRepository sharedInstance] saveUser:user];
}

- (void)tryToInvokeCallback:(LoginSystemLoginCallback)callback error:(NSError *)error {
    if(callback)
        callback(error == nil,error);
}

@end

You can simply change that class to apply the Dependency Injection rule:

typedef void(^LoginSystemLoginCallback)(BOOL success, NSError *error);

@interface LoginSystem : NSObject
@property(nonatomic, strong) id<APIConnection> apiConnection;
@property(nonatomic, strong) id<UserRepository> userRepository;
@property(nonatomic, strong) id<UserParser> parser;

- (instancetype)initWithApiConnection:(id <APIConnection>)apiConnection userRepository:(id<UserRepository>)userRepository parser:(id <UserParser>)parser;
- (void)loginWithRequest:(LoginRequest *)request callback:(LoginSystemLoginCallback)callback;
@end
@implementation LoginSystem
- (instancetype)initWithApiConnection:(id <APIConnection>)apiConnection userRepository:(id<UserRepository>)userRepository parser:(id <UserParser>)parser {
    self = [super init];
    if (self) {
        self.apiConnection = apiConnection;
        self.userRepository=userRepository;
        self.parser=parser;
    }

    return self;
}

- (void)loginWithRequest:(LoginRequest *)request  callback:(LoginSystemLoginCallback)callback{
    [self.apiConnection invokeRESTRequest:request success:^(id response) {
        [self storeUserFromResponse:response];
        [self tryToInvokeCallback:callback error:nil];
    }                             failure:^(NSError *error) {
        [self tryToInvokeCallback:callback error:error];
    }];
}

- (void)storeUserFromResponse:(id)response {
    User *user = [self.parser parseResponse:response];
    [self.userRepository saveUser:user];
}

- (void)tryToInvokeCallback:(LoginSystemLoginCallback)callback error:(NSError *)error {
    if(callback)
        callback(error == nil,error);
}
@end

Pretty simple, isn’t it? Instead of instantiating dependencies inside the LoginSystem class replace them with properties and export them to header file! When it comes to creating an instance of the LoginSystem class, just assign values to proper properties.

Look how UserRepository is initialized in the first example. We can guess with a hight probability that it is a singleton, because of the method name. In DI’s example, you wouldn’t say anything like that based just only on this piece of code. I’ll come back to singletons in further chapters.

Why should you obey Dependency Injection principle?

There are mainly 2 benefits from what we did:

  • The LoginSystem class is much easier to test – In the first version of LoginSystem to test it we need to take care of how to connect with API, how to parse a JSON and how to save it. Why? We cannot mock its dependencies because they are not public, so we need to test LoginSystem as a big object. After we extracted its dependencies to public properties we can now inject mocks and check if the LoginSystem called a method of APIConnection to download a User, then if it parsed the User by calling a UserParser method and then if it saved the User.
  • We can change details of UserRepository without changing any lines in LoginSystem – Right now UserRepository uses an NSUserDefaults to store a user object. It could be enough for some kind of application, but usually we would like to use CoreData to take care of persistence layer. What we can do now is to create a CoreDataUserRepository class. Conforms it to UserRepository protocol and inject it to LoginSystem. We switched from using NSUserDefaults to CoreData without any change in LoginSystem. How cool is that?

Init… init… init… and init – Inits everywhere!

DI leads to one problem. When it comes to creating an object you will have long list of inits. In my last project, it could look like this:

[[SCRDocumentsInteractor alloc] initWithDocumentCenterFacade:
[[SCRDocumentCenterFacade alloc] initWithDocumentProvider:
[[SCRAPIDocumentProvider alloc] initWithConnection:
[[SCRAPIConnectionAFNetworking alloc] initWithBaseURL:
[NSURL URLWithString:@"http://url"]] parser:
[[SCRMantleJSONDocumentParser alloc] init]] documentDeleter:
[[SCRDocumentDeleter alloc] initWithApiConnection:
[[SCRAPIConnectionAFNetworking alloc] initWithBaseURL:
[NSURL URLWithString:@"http://url"]]]] userPersistenceReader:
[[SCRMagicalRecordUserPersistenceReader alloc] 
initWithManagedObjectContext:managedObjectContext]];

Typhoon – Dependency injection framework for iOS

Luckily there is a tool which can help you to avoid so messy inits – it’s called Typhoon.

Assemblies

Heart of Typhoon is an assembly … or maybe are assemblies. Those classes collect dependencies and linked them together. I like to create one assembly per each module. I do so, because of readability. I don’t want to create 1000+ lines files.

Keep one thing in mind. To make Assemblies work you need to activate them. The simplest way to do so is to add TyphoonInitialAssemblies key to your .plist file. This key takes an array of assembly names (strings) as an argument.

To create an assembly just create a class and extend the TyphoonAssebmly class. Now you need to create TyphoonDefinitions. Typhoon doesn’t create objects immediately. It creates them when they are actually needed. TyphoonDefinitions describe how to create objects and how to link them.

- (AppDelegate *)appDelegate{
    return [TyphoonDefinition withClass:[AppDelegate class] configuration:^(TyphoonDefinition *definition) {
        [definition injectProperty:@selector(someObjectNeededForSomething) with:[self someObject]];
    }];
}

- (SomeObject *)someObject {
    return [TyphoonDefinition withClass:[SomeObject class]];
}

Typhoon will create an instance of AppDelegate with dependencies which we want to have ;).

Let’s inject some real stuff

AppDelegate was just an example. Now is the time to create a ViewController with all it’s dependencies.

@implementation LoginAssembly

- (LoginViewController *)loginViewController {
    return [TyphoonDefinition withClass:[LoginViewController class] configuration:^(TyphoonDefinition *definition) {
        [definition injectProperty:@selector(loginSystem) with:[self loginSystem]];
    }];
}

- (LoginSystem *)loginSystem {
    return [TyphoonDefinition withClass:[LoginSystem class] configuration:^(TyphoonDefinition *definition) {
        [definition useInitializer:@selector(initWithApiConnection:) parameters:^(TyphoonMethod *initializer) {
            [initializer injectParameterWith:[self.applicationAssembly apiConnection]];
        }];

        [definition injectProperty:@selector(delegate) with:[self loginViewController]];
    }];
}

@end
@implementation ApplicationAssembly
- (id <APIConnection>)apiConnection {
    return [TyphoonDefinition withClass:[AFNetworkingAPIConnection class] configuration:^(TyphoonDefinition *definition) {
        [definition useInitializer:@selector(initWithBaseURL:) parameters:^(TyphoonMethod *initializer) {
            [initializer injectParameterWith:TyphoonConfig(@"APIBaseURL")];
        }];
    }];
}
@end

This is everything. Now when it comes to instantiating LoginViewController Typhoon will automatically inject a LoginSystem object to it! Ok … it’s nearly everything 😛

Typhoon will automatically link your view controller definition inside assembly if you use TyphoonStoryboard object to create the view controller. It is simpler than it sounds. You just need to inject the storyboard object to AppDelegate by following lines:

@implementation ApplicationAssembly

- (AppDelegate *)appDelegate{
    return [TyphoonDefinition withClass:[AppDelegate class] configuration:^(TyphoonDefinition *definition) {
        [definition injectProperty:@selector(someObjectNeededForSomething) with:[self someObject]];
        [definition injectProperty:@selector(storyboard) with:[self.storyboardAssembly mainStorybaord]];
    }];
}
[...]
@end
@implementation StoryboardAssembly
- (UIStoryboard *)mainStorybaord {
  return [TyphoonDefinition withClass:[TyphoonStoryboard class] configuration:^(TyphoonDefinition *definition) {
      [definition useInitializer:@selector(storyboardWithName:factory:bundle:) parameters:^(TyphoonMethod *initializer) {
          [initializer injectParameterWith:@"Main"];
          [initializer injectParameterWith:self];
          [initializer injectParameterWith:[NSBundle mainBundle]];
      }];
  }];
}
[...]
@end

Scopes

Singletons – they are hated and loved objects ;). I use them but only when I really need them. However, I like to say „it’s ok to use singletons but an object which uses the singleton should not know that it uses a singleton”. How to achieve that? Just continue reading ;).

In Typhoon when you define TyphoonDefinition you can set a scope. The scope defines if or what reference type will Typhoon keep to the created object. It’s an enum with following values:

  • TyphoonScopePrototype – it creates a new instance of class at every call
  • TyphoonScopeSingleton – it creates a single instance of class and it keeps strong reference to it
  • TyphoonScopeLazySingleton – the same as above, but it is initialized when it’s called for the first time
  • TyphoonScopeWeakSingleton – the same as TyphoonScopeSingleton but it keeps weak reference
  • TyphoonScopeObjectGraph – default scope of Typhoon. It creates a new instance of class nearly at every call. It makes it possible to create circular dependencies.

Now it is a time to explain the above sentence.

Typhoon gives you a possibility to create object which will be a singleton. Now you can inject it to objects which gonna need that dependency. Done! Your object uses a singleton. Does he know that he uses a singleton instance? No, it doesn’t! You don’t call [MyObject sharedInstance] anywhere inside the objects class. Why is it so cool? You can postpone the decision if it is needed to have a singleton instance! You write a class like you would never use a singleton. When it occurs to you … „Hey, I definitely need a singleton here” then you can just change the scope of its definition. definition.scope = TyphoonScopeSingleton

Circular dependency

In above chapter I used „Circular dependency” phrase. The circular dependency exists when 2 classes have references to each other. It is not so rare case. When you use a delegate you can have such chance. If Typhoon will always create a new instance of a class it would be very problematic to connect 2 objects in both ways. Definition with scope TyphoonScopeObjectGraph will link them in both ways. Go few paragraph up to see a code example.

Circular dependency with delegate
Circular dependency with delegate

Last word

Dependency injection is not difficult pattern to understand. It can be explained with one sentence „The object should rely on it’s dependencies, but it shouldn’t instantiate them.”.

However, it is helpful to use some Dependency Injection Frameworks to get rid of long list of inits. One of such libraries for iOS is Typhoon. It is a kind of factory which creates objects when they are actualy needed. Our duty, as developers, is to create TyphoonDefinition to tell Typhoon what it should create.

I’ve been using Typhoon for about last 6 months and I think it is very handy to use. Moreover it’s powerful tool and I’ve skipped few possibilities to make the article relatively short. If you have any question just write it in comments. I’ll answer for it with pleasure!

At the end, I would like to ask you for something. If you find this article interesting, help others to reach it as well. Share the link on Twitter, Facebook or any other places you can! Thanks!

You can find project with above examples on my GitHub.

What next?

Recently I’ve configured the fastlane with our Jenkins and I’m really amazed how delivery process can be automated. However, I’m just after 4-month project where I used Viper as general architecture for the app. I’m quite happy how the app looks right now, but I had few very big problems with Viper assumptions. What would you like to read about in next article? My thought about Viper or how I configured fastlane? I wait for your comments or tweets.