View on GitHub

Alchemic v2.1

An advanced DI framework for iOS

Carthage compatible

API: Objective-C Swift 3

HomeInstallationAdding AlchemicArchitectureObject factoriesInjectionsValue typesProgrammatic usageiOS featuresAdvanced usageError handlingReference

Interfacing with Alchemic

Now that we know how to declare objects and inject them, lets look at how we retrieve objects in classes and code which is not managed by Alchemic. In other words, how to get Alchemic to work with the rest of your app.

Manual dependency injections

Alchemic will automatically inject dependencies into any object it instantiates or manages. There may be situations where you need to create objects independently and would still like Alchemic to handle the injection of dependencies. This is where AcInjectDependencies can be used.

-(instancetype) initWithFrame:(CGRect) aFrame {
    self = [super initWithFrame:aFrame];
    if (self) {
        AcInjectDependencies(self);
    }
    return self;
}
func init(frame:CGRect) {
    AcInjectDependencies(self)
}

You can call AcInjectDependencies anywhere in your code and pass it the object whose dependencies you want injected. Alchemic will search for a matching Class factory and use that to inject any dependencies specified in the model.

Getting objects

Sometimes (in unit tests for example) you want to get an object from Alchemic without setting up an injection. AcGet allows you to search for and return an object (or objects).

-(void) myMethod {
    NSDateFormatter *formatter = AcGet(NSDateFormatter, AcName(@"JSON date formatter"));
    // Do stuff ....
}
func myMethod() {
    var formatter:NSDateFormatter = AcGet(AcName(@"JSON date formatter"))
    // Do stuff ....
}

In Objective-C AcGet requires the first argument to be the type that will be returned. This type is needed because the runtime cannot tell Alchemic what is expected and it needs this information to finish processing the results. Especially if you are expecting an array back.

Arguments after the type are search criteria used to find candidate object factories. So AcClass, AcProtocol or AcName can all be used to search the model for objects.

Note: It makes no sense to allow any of Alchemic’s constants here so only model search criteria are allowed.

Note that AcGet also does standard Alchemic NSArray processing. For example the following code will return an array of all Alchemic registered date formatters:

-(void) myMethod {
NSArray *formatters = AcGet(NSArray, AcClass(NSDateFormatter));
// Do stuff ....
}
func myMethod() {
var formatters = AcGet(NSArray.self, source:AcClass(NSDateFormatter))
// Do stuff ....
}

Finally, you can leave out the search criteria macros like this:

-(void) myMethod {
    NSDateFormatter *formatter = AcGet(NSDateFormatter);
    // Do stuff ....
}
func myMethod() {
    var formatter = AcGet(NSDateFormatter.self)
    // Do stuff ....
}

Without any criteria, Alchemic will use the return type to determine the search criteria for scanning the model based in it’s class and any applicable protocols.

Setting objects

Alchemic also provides a function for setting objects called AcSet. Mostly it’s used for storing objects in factories which have been setup as reference types using AcReference:

-(void) myMethod {
    NSDateFormatter *formatter = ...; // Create a formatter
    AcSet(NSDateFormatter, AcName(@"myDateFormatter"));
}
func myMethod() {
    var formatter = ...; // Create a formatter 
    AcSet(NSDateFormatter.self, AcName("myDateFormatter"))
}

AcSet locates a matching object factory from the criteria passed as arguments after the object to set into the model. ACName is most useful when setting values as AcSet expects there to be only one object factory found from the criteria. If zero or more than one object factory is found, AcSet will throw an error.

Note: that setting a new object for an object factory does not effect any previously injected references to the old object. Only injections done after setting the new object will receive it.

Invoking methods

AcInvoke is for when you want to call a declared method or initializer manually. It’s mostly useful when you want to call it, but still let Alchemic handled the method arguments. For example, you might declare a factory initializer like this:

AcInitializer(initWithText:, AcFactory, AcArg(NSString, AcValue(@"Default message")
-(instancetype) initWithText:(NSString *) message {
    // ...
}
static func alchemic( _ of: ALCClassObjectFactory) {
    AcInitializer(of, initializer:"initWithMessage:", 
        args:AcArg(NSString.self, source:AcValue(@"Default message"))
    )
}
func init(message:NSString) {
    // ...
}

In this scenario you want the factory method to give you a new instance of the object when you need it, but with a different message. So you can call it like this:

-(void) myMethod {
    MyObj *myObj = AcInvoke(AcName(@"MyObj initWithText:"), @"Message text");
    // Do stuff ....
}
func myMethod() {
    var myObj = AcInvoke(AcName("MyObj initWithText:"), args:"Message text")
    // Do stuff ....
}

AcInvoke will locate all Alchemic object factories that match the first argument, which must be a search function. Normally it’s AcName because the usual scenario is to be address a specific method or initializer. Once Alchemic has located the object factory for the method you want, it then invokes it (in the case of a normal method) or creates an instance using it if it is an initializer. In either case the method being addresses must have been registered via AcInitializer or AcMethod so it can be found in the model and called.

Also note in the above example, we are using the default name for the method generated by Alchemic. Using AcInvoke is one good reason to make use of AcFactoryName to add custom names to registrations.

Callbacks and notifications

AlchemicAware protocol

The protocol AlchemicAware contains several callback methods you can implement in your classes. These methods are executed at specific times to allow your code to respond if you need it. To use them, just create the protocol’s method in your class. Actually specifying the AlchemicAware protocol is optional. Alchemic will automatically look for these methods regardless.

-(void) alchemicDidInjectVariable:(NSString *) variable { 
    // ... 
}
@objc func alchemicDidInjectVariable(variable:NSString) { 
    // ... 
}

Called after Alchemic has injected a variable. The variable name is passed as an argument. This is similar to the way KVO calls after a property has been set.

-(void) alchemicDidInjectDependencies {
    // ...
}
func alchemicDidInjectDependencies() {
    // ...
}

Called after all injections for an object have been done. This is the ideal place to perform further configuration.

Alchemic notifications

The following is a list of notifications that Alchemic sends out.

Notification Description
AlchemicDidCreateObject Sent after Alchemic has finished instantiating an object.
AlchemicDidFinishStarting Sent after Alchemic has finished it’s startup. At this point the model will have been populated and resolved, and all singletons will have been instantiated. This notification is a good way to know when you can run code that needs to execute as soon as Alchemic is ready to serve objects.
AlchemicDidStoreObject Sent after an object has been stored in the model. This is mainly used internally so that Alchemic can trigger fresh injections based on the new value.