OCInject 0.3.0

OCInject 0.3.0

TestsTested
LangLanguage Obj-CObjective C
License Custom
ReleasedLast Release Dec 2014

Maintained by Unclaimed.



OCInject 0.3.0

  • By
  • Ivan Korobkov

Objective-C dependency injection framework. Not a Guice or Spring port. Better. Simpler.

Key features

  • Fast and small.
  • Really easy to use.
  • Does not steal [[Class alloc]initWithSomeArgs] from you.
  • Does not manage your application object graph.
  • Injects dependencies everywhere via OCInject(Class) and OCInjectProtocol(protocol) functions.
  • Provides an optional category method [NSObject inject] equivalent to OCInject(class).
  • Requires almost zero configuration because of runtime bindings.
  • Transparently integrates into tests/mocks.
  • Thread-safe.
  • Supports iOS 6+ and OSX 10.8+ with ARC.

Usage

Install from CocoaPods:

pod 'OCInject'

Code:

#import "OCInject.h"

// Given some dependencies.
@interface Cache : NSObject
@end

@interface Servcie : NSObject
@end

@protocol Validator
@end


// Configure a shared injector in your app delegate.
// Typically, almost zero configuration is required because of automatic runtime bindings.
@implementation MyAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    [OCInjector configure:^(OCInjectBinder *binder) {
        [binder installModule:[[MyModule alloc] init]];     // Include bindings from a module.

        // Add some bindings.
        [binder bindClass:UIApplication.class toInstance:[UIApplication sharedApplication]];
        [binder bindClass:NSNotificationCenter.class toInstance:[NSNotificationCenter defaultCenter]];
        [binder bindProtocol:@protocol(Validator) toConstructor:^() {
            return [[MyValidator alloc] init];
        }]
    }];

    return YES;
}
@end


// Inject the dependencies wherever you need.
@implementation MyViewController {
    Cache *_cache;
    Service *_service;
    UIApplication *_app;
}

// In constructors.
- (instancetype)initWithCoder:(id)decoder {     // Invoked by a storyboard.
    if (self = [super initWithCoder:decoder]) {
        _app = [UIApplication inject];
        _cache = [Cache inject];
        _service = OCInject(Service.class);
    }

    return self;
}

// In methods and fuctions.
- (void)myMethod {
    id<Validator> validator = OCInjectProtocol(@protocol(Validator));
}
@end

Testing

OCInject is very easy to use in tests. Configure a new injector in setUp with some test&mock bindings and optionally clear it in tearDown.

@interface MyViewControllerTests : XCTestCase
@end

@implementation MyViewControllerTests
- (void)setUp {
    // Using OCMockito for mocks.

    [OCInjector clearAndConfigure:^(OCInjectBinder *binder) {
        [binder bindClass:Cache.class toInstance:mock(Cache.class)];
        [binder bindProtocol:@protocol(Validator) toInstance:mockProtocol(@protocol(Validator))];
    }];
}

- (void)tearDown {
    // This is absolutelly optional.
    [OCInjector clear];
}
@end

Thread-safety

After configuration the injector is thread-safe and can be used by multiple threads.

Bindings

OCInject supports several binding types which specify how instances are created:

  • Instance bindings which always return the same instances:

    [OCInjector configure:^(OCInjectorBinder *binder) {
        [binder bindClass:NSNumber.class toInstance:@(123)];
    }];
  • Constructor bindings which create singletons on first injection.

    [OCInjector configure:^(OCInjectorBinder *binder) {
        [binder bindClass:Cache.class toConstructor:^() {
            // Executed only once on first injection.
            return [[Cache alloc] initWithArg:123];
        }];
    }];
  • Provider bindings which are executed on every injection.

    [OCInjector configure:^(OCInjectorBinder *binder) {
        [binder bindClass:CurrentUser.class toProvider:^() {
            // Executed on every injection.
            return [CurrentUser randomUser];
        }];
    }]
  • Runtime bindings which are automatically created for classes when no explicit bindings exist. The classes are created as [[Class alloc] init] singletons. Below no bindings are required for the PriceFormatter.

      @interface PriceFormatter : NSObject
      - (instancetype)init;
      @end
    
      @implementation ProductsController  // UITableViewController subclass
      - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
          PriceFormatter *formatter = [PriceFormatter inject];
          // create and return a product cell.
      }
      @end

Modules

Modules are optional as you can configure the injector with a single block of code. However, sometimes modules are usefull when you need to group some bindings together.

@interface MyModule : OCInjectModule
@end

@implementation MyModule
- (void)configure:(OCInjectBinder *)binder {
    // Module bindings.
    [binder bindProtocol:@protocol(MyService) toConstructor:^() {
        return [[MyServiceClient alloc] initWithUrl:@"http://example.com/"];
    }]
}
@end

Install modules to a binder during configuration:

[OCInject configure:^(OCInjectBinder *binder) {
    [binder installModule: [[MyModule alloc] init]];
    // Other bindings.
}

Why no scopes?

I've used Guice and Spring for a lot of years, and I don't like their scopes (the concept). Moreover, some scopes such as a request scope or a session scope are fragile, introduce high coupling and are difficult to test. OCInject uses custom constructors and providers which are way more flexible than scopes. Need a scope? Write a provider.

Stability

OCInject is stable and well-tested. It is a port of a mature DI framework python-inject specifically tailored to Objective-C. If you find a bug, please, report it.

Links

License

Apache License 2.0