Skip to content

p-r-o-m-e-t-h-e-u-s/CDI

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CDI

Context and Dependency Injection for Objective-C

Version Platform

Simple, easy and very powerful way to use context and dependency injection and interception for objective c development. CDI is designed to solve some common software development patterns like Dependency Injection / Inversion of Control, Singleton and Interception (an minimalistic AOP approach). It follows convention over configuration and performs just on runtime.

The main features are:

  • Injection by annotation
  • Component auto-wiring
  • Manual object binding
  • Singleton by annotation
  • Interception by annotation

Using CDI will reduce the boilerplate code in many classes, increase readability and allow better testing. The interception functionality will also provide the ability to separate the implementation code by aspects like security, logging and other facets.

CDI does not depend on another framework, which means you can use any unit testing, mocking or other framework (see Limitation chapter).

Here are some samples:

Sample 1: Simple injection using auto-wiring

@interface InjectExample: NSObject
// Let's say you have one class which implements the MyServiceProtocol
...
@property(nonatomic, readwrite) id<MyServiceProtocol> *myService;
...
@end
    
@implementation InjectExample
...
// Using @inject instead of @synthesize will lookup the 
// implementation class at runtime and create the instance automatically
@inject(myService);
...
@end

Discussion:

@inject will find any suitable implementations wich conforms to MyServiceProtocol, create an instance automatically and assign it to the instance variable or property like myService. Unless there is just one suitable implementation, the instance can be created without further configuration. This is called auto-wiring by convention over configuration. If multiple implementations are available, CDI will throw an CDIException because it cannot determine the right implementation. In this case manual wiring or binding is required.

Full Sample Code:

Sample 2: Simple injection using manual wiring

@interface InjectExample: NSObject
...
// Let's say you have multiple classes which implements the MyServiceProtocol
@property(nonatomic, readwrite) id<MyServiceProtocol> *myService;
...
@end
    
@implementation InjectExample
...
// Using @inject with an implementation class which will be
// used to create the myService instance 
@inject(myService, MyServiceImplementation);
...
@end

Discussion:

@inject will create an instance of MyServiceImplementation and assign it to the instance variable or property like myService. This is useful if you exactly know the implementation class of the instance variable type. Also if multiple possible implementations are available at runtime, this has to be used to define the relevant implementation class.

Full Sample Code:

Sample 3: Simple injection with classes

@interface InjectExample: NSObject
...
// Let's say you have a property with a class type
@property(nonatomic, readwrite) NSDate *now;
...
@end
    
@implementation InjectExample
...
// Using @inject will create a new instance automatically
// containing the current date and time 
@inject(now);
...
@end

Discussion:

@inject will create an instance of NSDate and assign it to the instance variable or property like now. In this case the implementation is identical to the instance variable type, which is used to create the instance. CDI will use the default instantiation method -(id) init to complete the object creation.

The example @inject(now); is a reduced code of synthesize now; and _now = [[NSDate alloc] init];

Full Sample Code:

Sample 4: Simple singleton implementation

@interface MySample4ServiceImplementation : NSObject <MySample4Service>
...
@end

// Define MySample4ServiceImplementation as a singleton
@singleton(MySample4ServiceImplementation);

@implementation MySample4ServiceImplementation
...
@end

Discussion:

@singleton will augment the class MySample4ServiceImplementation so that injecting an instance will always apply the unique instance. This fourth sample implementation is identical with the first sample, except that @inject(myService); will create just once an instance for the lifetime of the application.

Full Sample Code:

Sample 5: Interceptor implementation

// This is a very simple method execution logger.
@interface MethodLoggerInterceptor : CDIInterceptor
@end

@implementation MethodLoggerInterceptor
// The invoke method is called on each method call.
-(void)invoke:(CDIInvocationContext *)context {
   	NSLog(@"--> Entering [%@:%@]", context.target, context.method);
   	[context execute];
   	NSLog(@"<-- Leaving  [%@:%@]", context.target, context.method);
}
@end
	
@interface MyDemo : NSObject {
	...
	-(void)doDemo;
	...
}
	
@intercept(MyDemo,MethodLoggerInterceptor)
	
@implementation MyDemo
...
// This method will be surrounded automatically with entering and leaving log messages.
-(void)doDemo {
   	...
}
...
@end

Discussion:

Interceptors are very useful to separated code with different aspects. For security reasons a Facade or Input Validation can easily be integrated using interceptors. Or Logging, tracing and profiling can easily be activated at runtime. All these and other aspects can smartly be separated from the application logic.

Full Sample Code:

Sample 6: Simple manual binding

// Override the auto-wiring by binding new implementation classes to use mocking objects.
// Your test just needs to use the bindProtocol or bindClass method in the setup of your
// Unit testing framework.
[[CDIInjector sharedInstance] bindProtocol:@protocol(ExampleProtocol) with:[MyMock class]];

Discussion:

Manual binding can be used to define a specific implementation. This can be very useful to exchange a implementation with a mock.

Full Sample Code:

Usage

To run the example project; clone the repo, and run pod install from the Project directory first.

The injected instance variables are created before the class instance initialization methods (init...) and therefore can be used within. The interceptors are instantiated after the class instance initialization methods (init...) and are therefore perform after the instance creation.

Limitation

CDI is under development and there still may be some unknown issues.

Known issues are:

Open:

  • Compatibility problems with OCMock

Fixed:

  • Subclasses of UIViewController currently do not support interception. [FIXED 1.0.0-beta3]

Please report issues here.

Installation

CDI is available through CocoaPods, to install it simply add the following line to your Podfile:

pod "CDI"

CDI has to be enabled before it can be used for development. Open the AppDelegate.m (or any similar class which is executed at the beginning of the application) and add the following initialize method to the implementation:

#import "AppDelegate.h"
#import <CDI.h>

@implementation AppDelegate
	
...
	
+(void)initialize {
    [super initialize];
    // Enable context and dependency injection
    [CDI initialize];
}
	
@end

Full Sample Code:

License

CDI is available under the MIT license. See the LICENSE file for more information.

About

Context and Dependency Injection for Objective-C

Resources

License

Stars

Watchers

Forks

Packages