Blog by Railsware

Dependency Injection in Objective-C with Blood and Magic

MVC is not sufficient

Every day iOS apps become more cumbersome and bulky, which makes MVC approach not sufficient.

We are seeing more and more classes for different purposes: app logic extracts into services, models extends with decorators, view separates into partials and so on. And – what is more important – there are now a lot of dependencies that need to be managed somehow.

Singleton is often used to resolve this problem; this is a kind of global variable that everyone can access. How many times have you seen such a code:

[[RequestManager sharedInstance] loadResourcesAtPath:@"http://example.com/resources" withDelegate:self];
// or
[[DatabaseManager sharedManager] saveResource:resource];

A lot of projects use this approach, but it entails a few flaws:

The first problem is easy to solve – use object property:

@interface ViewController : UIViewController

@property (nonatomic, strong) RequestManager *requestManager;

@end

But this approach entails another issue – someone should ‘fill’ this property.

Dependency Injection

These issues are not unique for Objective-C. If we take a look at more ‘industrial’ languages, e.g. Java or C++, we can find a solution. Widely used approach in Java – Dependency Injection(DI).

DI allows injecting requestManager as a singleton in the application, but uses mock object while testing. No one is aware of singleton however – neither RequestManager, nor ViewController – because all this stuff is managed by DI framework.

You can find a lot of Objective-C implementations of the Dependency Injection pattern on GitHub, but they have some disadvantages:

BloodMagic

So, let’s look into another framework (also with its set of disadvantages) – BloodMagic.

BloodMagic provides some sort of custom attributes. It’s designed to be extensible, so more features are coming soon. Currently, only one attribute is implemented – Lazy.

This attribute allows to initialize objects on demand with a minimum of boilerplate code. Therefore, instead of the following sheets:

@interface ViewController : UIViewController

@property (nonatomic, strong) ProgressViewService *progressViewService;
@property (nonatomic, strong) ResourceLoader *resourceLoader;

@end

@implementation ViewController

- (void)loadResources
{
    [self.progressViewService showProgressInView:self.view];
    
    self.resourceLoader.delegate = self;
    [self.resourceLoader loadResources];
}

- (ProgressViewService *)progressViewService
{
    if (_progressViewService == nil) {
        _progressViewService = [ProgressViewService new];
    }
    
    return _progressViewService;
}

- (ResourceLoader *)resourceLoader
{
    if (_resourceLoader == nil) {
        _resourceLoader = [ResourceLoader new];
    }
    
    return _resourceLoader;
}

@end

we can simply write:

@interface ViewController : UIViewController
    

@property (nonatomic, strong) ProgressViewService *progressViewService;
@property (nonatomic, strong) ResourceLoader *resourceLoader;

@end

@implementation ViewController

@dynamic progressViewService;
@dynamic resourceLoader;

- (void)loadResources
{
    [self.progressViewService showProgressInView:self.view];
    
    self.resourceLoader.delegate = self;
    [self.resourceLoader loadResources];
}

@end

And that’s it. Both @dynamic properties are created by a call to self.progressViewService and self.resourceLoader. They are released in the same way as a standard properties – after ViewController is deallocated.

BloodMagic and Dependency Injection

+new method is used for object creation here by default. But it also has a capability of writing custom initializers which is a key feature of BMLazy as a DI framework.

Creating custom initializer is a bit cumbersome, but works well:

BMInitializer *initializer = [BMInitializer lazyInitializer];
initializer.propertyClass = [ProgressViewService class];
initializer.initializer = ^id (id sender){
  return [[ProgressViewService alloc] initWithViewController:sender];
};
[initializer registerInitializer];

propertyClass – initializer registers for property of this class.

initializer – block, which is called to initialize property. If this block is nil, or initializer is not found for concrete property, then object is created via +new class method.

sender – container class instance.

Also, initializer has a containerClass property, which allows us to describe creation of the same property based on class container. For example:

BMInitializer *usersLoaderInitializer = [BMInitializer lazyInitializer];
usersLoaderInitializer.propertyClass = [ResourceLoader class];
usersLoaderInitializer.containerClass = [UsersViewController class];
usersLoaderInitializer.initializer = ^id (id sender){
  return [ResourceLoader usersLoader];
};
[usersLoaderInitializer registerInitializer];

BMInitializer *projectsLoaderInitializer = [BMInitializer lazyInitializer];
projectsLoaderInitializer.propertyClass = [ResourceLoader class];
projectsLoaderInitializer.containerClass = [ProjectsViewController class];
projectsLoaderInitializer.initializer = ^id (id sender){
  return [ResourceLoader projectsLoader];
};
[projectsLoaderInitializer registerInitializer];

Thus, for UsersViewController and ProjectsViewController different objects are created. By default containerClass equals to NSObject class.

Initializers help to get rid of various shared* methods and hardcoding, described in the beginning:

BMInitializer *initializer = [BMInitializer lazyInitializer];
initializer.propertyClass = [RequestManager class];
initializer.initializer = ^id (id sender){
  static id singleInstance = nil;
  static dispatch_once_t once;
  dispatch_once(&once, ^{
    singleInstance = [RequestManager new];
  });
  return singleInstance;
};
[initializer registerInitializer];

Organize initializers

We may have a lot of initializers, so it makes sense to move them into a separate place.

Nice solution is to store them in different files and use attributes of a compiler (it’s a normal practice). BloodMagic has also a simple macros that hides this attribute – lazy_initializer. We should simply create a source file without header and add it to the compilation phase.

Here is an example:

//  SomeInitializer.m

#import <BloodMagic/Lazy.h>

#import "ResourceLoader.h"
#import "UsersViewController.h"
#import "ProjectsViewController.h"

lazy_initializer ResourseLoaderInitializers()
{
    BMInitializer *usersLoaderInitializer = [BMInitializer lazyInitializer];
    usersLoaderInitializer.propertyClass = [ResourceLoader class];
    usersLoaderInitializer.containerClass = [UsersViewController class];
    usersLoaderInitializer.initializer = ^id (id sender){
        return [ResourceLoader usersLoader];
    };
    [usersLoaderInitializer registerInitializer];
    
    BMInitializer *projectsLoaderInitializer = [BMInitializer lazyInitializer];
    projectsLoaderInitializer.propertyClass = [ResourceLoader class];
    projectsLoaderInitializer.containerClass = [ProjectsViewController class];
    projectsLoaderInitializer.initializer = ^id (id sender){
        return [ResourceLoader projectsLoader];
    };
    [projectsLoaderInitializer registerInitializer];
}

lazy_initializer will be replaced with __attribute__((constructor)) static void. Attribute constructor means that this method will be called before main function (here are details: GCC. Function Attributes).

Exit mobile version