TestsTested | ✓ |
LangLanguage | Obj-CObjective C |
License | Custom |
ReleasedLast Release | Dec 2014 |
Maintained by Unclaimed.
Objective-C dependency injection framework. Not a Guice or Spring port. Better. Simpler.
[[Class alloc]initWithSomeArgs]
from you.OCInject(Class)
and OCInjectProtocol(protocol)
functions.[NSObject inject]
equivalent to OCInject(class)
.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
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
After configuration the injector is thread-safe and can be used by multiple threads.
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 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.
}
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.
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.
Apache License 2.0