Objection is a lightweight dependency injection framework for Objective-C for MacOS X and iOS. For those of you that have used Guice objection will feel familiar. Objection was built to stay out of your way and alleviate the need to maintain a large XML container or manually construct objects.
- "Annotation" Based Dependency Injection
- Seamless support for integrating custom and external dependencies
- Custom Object Providers
- Meta Class Bindings
- Protocol Bindings
- Instance Bindings
- Lazily instantiates dependencies
- Eager Singletons
A class can be registered with objection using the macros objection_register or objection_register_singleton. The objection_requires macro can be used to declare what dependencies objection should provide to all instances it creates of that class. objection_requires can be used safely with inheritance.
@class Engine, Brakes;
@interface Car : NSObject
{
Engine *engine;
Brakes *brakes;
BOOL awake;
}
// Will be filled in by objection
@property(nonatomic, retain) Engine *engine;
// Will be filled in by objection
@property(nonatomic, retain) Brakes *brakes;
@property(nonatomic) BOOL awake;
@implementation Car
objection_register(Car)
objection_requires(@"engine", @"brakes")
@synthesize engine, brakes, awake;
@end
An object can be fetched from objection by creating an injector and then asking for an instance of a particular class or protocol. An injector manages its own object context. Which means that a singleton is per injector and is not necessarily a true singleton.
- (void)someMethod {
JSObjectionInjector *injector = [JSObjection createInjector];
id car = [injector getObject:[Car class]];
}
A global injector can be registered with Objection which can be used throughout your application or library.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
JSObjectionInjector *injector = [JSObjection createInjector];
[JSObjection setGlobalInjector:injector];
}
- (void)viewDidLoad {
id myModel = [[JSObjection globalInjector] getObject:[MyModel class]];
}
Objection supports associating an object outside the context of Objection by configuring an JSObjectionModule.
- Bind a protocol or class to a specific instance of that type
- Bind a class that is registered with Objection to a protocol
@interface MyAppModule : JSObjectionModule {
}
@end
@implementation MyAppModule
- (void)configure {
[self bind:[UIApplication sharedApplication] toClass:[UIApplication class]];
[self bind:[UIApplication sharedApplication].delegate toProtocol:@protocol(UIApplicationDelegate)];
[self bindClass:[MyAPIService class] toProtocol:@protocol(APIService)];
}
@end
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
JSObjectionInjector *injector = [JSObjection createInjector:[[[MyAppModule alloc] init] autorelease]];
[JSObjection setGlobalInjector:injector];
}
There are times when a dependency -- usually external -- is implemented using only class methods. Objection can explicitly support binding to the meta class instance through a protocol. This avoids having to unnecessarily create a wrapper class that passes through to the class methods. The catch, of course, is that it requires a protocol definition so that Objection knows how to bind the meta class to objects in the injector context.
@protocol ExternalUtility
- (void)doSomething;
@end
@interface ExternalUtility
+ (void)doSomething;
@end
@implementation ExternalUtility
+ (void)doSomething {...}
@end
// Module Configuration
- (void)configure {
[self bindMetaClass:[ExternalUtility class] toProtocol:@protocol(ExternalUtility)];
}
@interface SomeClass
{
...
}
// Use 'assign' because a meta class is not subject to the normal retain/release lifecycle.
// It will exist until the application is terminated (Class Initialization -> Application Termination)
// regardless of the number of objects in the runtime that reference it.
@property (nonatomic, assign) id<ExternalUtility> externalUtility
@end
Occasionally you'll want to manually construct an object within Objection. Providers allow you to use a custom mechanism for building objects that are bound to a type. You can create a class that conforms to the ObjectionProvider protocol or you can use a block to build the object.
@implementation CarProvider
- (id)createInstance:(JSObjectionInjector *)context {
// Manually build object
return car;
}
@end
@implementation MyAppModule
- (void)configure {
[self bindProvider:[[[CarProvider alloc] init] autorelease] toClass:[Car class]];
[self bindBlock:^(JSObjectionInjector *context) {
// Manually build object
return car;
} toClass:[Car class]];
}
@end
You can mark registered singleton classes as eager singletons. Eager singletons will be instantiated during the creation of the injector rather than being lazily instantiated.
@implementation MyAppModule
- (void)configure {
[self registerEagerSingleton:[Car class]];
}
@end
If an object is interested in knowing when it has been fully instantiated by objection it can implement the method awakeFromObjection.
@implementation Car
//...
objection_register_singleton(Car)
- (void)awakeFromObjection {
awake = YES;
}
@end
- Resolve circular dependencies.
- Cache results of property definitions.
- Add contribution section
- Re-factor the method for declaring dependencies.
- The current implementation relies on extending (via objection_requires) the class interface
- The re-factored form should delegate directly to Objection (e.g. [JSObjection registerClass:[TheClass class] withDependencies:@"collaborator", nil])
- This form would allow for alternative registration mechanisms
git clone git://github.com/atomicobject/objection.git
git checkout 0.8.6
- rake artifact:ios or rake artifact:ios3 if you require iOS 3.0 compatibility
- cp -R build/Release-iphoneuniversal/Objection-iOS.framework ${DEST_DIR}
- Add -ObjC and -all_load to Other Link Flags in your project
#import <Objection-iOS/Objection.h>
- rake artifact:osx
- cp -R build/Release/Objection.framework ${DEST_DIR}
#import <Objection/Objection.h>
- There is a glitch in XCode that will cause header files to not be copied properly. So, if you are building the iOS target you may have to run the build process a couple of times to get all of the proper header files copied.
- MacOS X 10.6 +
- iOS 3.0 +
- Justin DeWind (dewind@atomicobject.com, @dewind on Twitter)
- © 2009-2011 Atomic Object
- More Atomic Object open source projects