InDependence is a Dependency Injection (DI) framework for iOS written in Objective-C. Key features are:
- Support for child-to-parent and other object-to-relatives references. It makes integration of DI to old legacy code much easier.
- Extendable design of framework encourages writing custom extensions for adapting InDependence to own needs.
Álpha. Public interface may change.
- "Annotation" based Dependency Injection
- Binding system
- Class bindings
- Protocol bindings
- Bindings may be grouped into modules
- Child-to-parent references
- Object-to-relatives references
- Initializer support
- Default and custom arguments
- Singleton annotations
- Objects are notified when injection is complete
- Extensions for adding own features
The independence_requirements
macros used to declare what dependencies InDependence should inject to all instances it creates of that class. independence_requirements
can be used safely with inheritance.
@class Wheel;
@interface Chassis : NSObject
// Will be injected by InDependence
@property(nonatomic,strong) Wheel *wheel;
@end
@interface Car : NSObject
// Will be injected by InDependence
@property(nonatomic,strong) Chassis *chassis;
// Will be injected by InDependence
@property(nonatomic,strong) Engine *engine;
@end
@implementation Chassis
independence_requirements(@"wheel");
@end
@implementation Car
independence_requirements(@"chassis",@"wheel");
@end
An object can be fetched by asking injector for an instance of a particular class or protocol. An injector manages its own context. It means that a singleton is per injector and is not necessarily a true singleton.
- (void)someMethod {
INDInjector *injector = [INDInjector sharedInjector];
Car *car = [injector getObject:[Car class]
parent:nil]; // Car is root object
}
Shared injector may be changed at any time:
INDInjector newInjector = [INDInjector new];
[INDInjector setSharedInjector:newInjector];
InDependence can automatically fill in references to parents, ancestors and any other relatives. References must be declared as weak
.
@interface Wheel()
// Will be filled in by InDependence
@property(nonatomic,weak) Chassis *chassis;
// Will be filled in by InDependence
@property(nonatomic,weak) Car *car;
@end
@interface Engine()
// Will be filled in by InDependence
@property(nonatomic,weak) Wheel *wheel;
@end
@implementation Wheel
independence_references(@"chassis",@"car");
@end
@implementation Engine
independence_references(@"wheel");
// ...
@end
If an object is interested in knowing when it has been fully instantiated by InDependence it can implement the method awakeFromInjector
.
@implementation Car
// ...
-(void)awakeFromInjector{
NSLog(@"%@ awaked",self);
}
@end
A module is a set of bindings which contributes additional configuration information to the injector. It is especially useful for integrating external dependencies and binding protocols to classes or instances.
@interface MyModule : INDModule
@end
@implementation MyModule
- (void)configure {
[self bindClass:[UnibodyChassis class] toClass:[Chassis class]];
[self bindClass:[HybridEngine class] toClass:[Engine class]];
}
@end
[[INDInjector sharedInjector] addModule:[MyModule new]];
Car *car = [[INDInjector sharedInjector] getObject:[Car class]
parent:nil];
NSLog(@"%@",NSStringFromClass([car.chassis class])); // UnibodyChassis
NSLog(@"%@",NSStringFromClass([car.engine class])); // HybridEngine
There may be binding rules for already binded classes (for example, in another module). InDependence finds appropriate class by recursion.
-(void)configure{
[self.injector bindClass:[Ford class] toClass:[Car class]];
[self.injector bindClass:[FordFocus class] toClass:[Ford class]];
}
// ...
Car *car = [[InDependenceInjector sharedInjector] getObject:[Car class]];
NSLog(NSStringFromClass([car class])); // FordFocus
By default, InDependence allocates objects with the default initializer init
. If you'd like to instantiate an object with an alternate initializer the independence_initializer
macros can be used to do so. The macros supports passing in default arguments (scalar values are not supported).
@interface ColoredCar : Car
-(id)initWithColor:(NSString *)color;
@property(nonatomic,copy) NSString *color;
@end
@implementation ColoredCar
independence_initializer(initWithColor:,@"Black");
-(id)initWithColor:(NSString *)color{
if (!(self = [super init])) return self;
_color = color;
return self;
}
@end
ColoredCar *carWithDefaultColor =
[[INDInjector sharedInjector] getObject:[ColoredCar class]
parent:nil];
NSLog(@"%@",carWithDefaultColor.color); // Black
ColoredCar *carWithCustomColor =
[[INDInjector sharedInjector] getObject:[ColoredCar class]
parent:nil
arguments:@"Red",nil];
NSLog(@"%@",carWithCustomColor.color); // Red
Any class may be annotated as a singleton using independence_singleton()
macros.
@interface Road : NSObject
@end
@implementation Road
independence_singleton();
@end
@interface Car()
@property(nonatomic,strong) Road *road;
@end
@implementation Car
independence_requirements(@"chassis",@"wheel",@"road");
// …
@end
Car *car = [[INDInjector sharedInjector] getObject:[Car class] parent:nil];
Car *anotherCar = [[INDInjector sharedInjector] getObject:[Car class] parent:nil];
NSLog(@"%d",[car.road isEqual:anotherCar.road]); // 1
Add sources from InDependence
folder to your project. Enable ARC for them. Add InDependence.h
to your project's .pch
file:
#import "InDependence.h"
- iOS 5.0 and later
- ARC
MIT License.
- Better README
- Objects Tree and correct parent specifying
- Protocol bindings
- Binded arguments
- Inline modules
- Extensions
- Bindings
- Instance bindings
- Custom factory blocks
- Custom post-init blocks
- References
- Dynamically provided references
Some code and ideas derived from Objection by Justin DeWind.