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

Managing the UIApplicationDelegate instance

Alchemic has some special processing for UIApplicationDelegate instances. After starting, Alchemic will automatically search for a UIApplicationDelegate instance and if it finds one, inject any dependencies it needs. So there is no need to add any AcRegister declarations to the app delegate class. Note: You can still use AcRegister to give the application delegate a name if you like.

Alchemic will also automatically add an object factory for the delegate to its model and set it with your app’s instance.

UIViewControllers and Story Boards

For the moment Alchemic does not do any automatic processing of UIViewController instances because it’s so easy to set them up. Instead, the simplest solution is to self inject in awakeFromNib or viewDidLoad like this:

-(void) viewDidLoad {
    AcInjectDependencies(self);
}
func viewDidLoad() {
    AcInjectDependencies(self)
}

*Note: that you can use AcSet instead of AcInjectDependencies if you also want Alchemic to inject the controller into other classes.

A Common Setup

Here’s a typical UIViewController setup:

@implementation MyViewController 

AcRegister(AcWeak, AcNillable)

-(void) viewDidLoad {
    [super viewDidLoad];
    AcSet(self);
}

@end

The logic behind the AcWeak and AcNillable is to ensure that the object factory for it will not cause a retain cycle. Nillable means that if this controller is injected into something, a nil injection is allowed. This does depend on what the value is being injected into.

Main Controllers

Often the main view controller of your app is instantiated from a story boards before Alchemic has had a chance to finish loaded and starting it’s model. Therefore trying to get injections done on this view controller via AcSet or AcInjectDependencies will throw an error.

In this case you need to wrap your code in AcWhenReady function like this:

-(void) viewDidLoad {
    AcWhenReady(^{
        AcInjectDependencies(self);
    });
}

AcWhenReady passes the block to Alchemic which doesn’t execute it until after Alchemic has finished starting and can perform any injections that the block may require.

Note: If Alchemic has managed to start before AcWhenReady is called, then it will execute the block immediately.

Key-Value stores

NSUserDefaults

Alchemic provides a set of tools for automatically managing data which you want stored in the user defaults area. Alchemics user defaults management feature is activated by adding the AcEnabledUserDefaults macro to any of your classes. You only need to add it once and Alchemic will automatically find it. For example:

@implementation MyClass
AcEnabledUserDefaults
@end
class Singleton:NSObject {
    static func alchemic( _ of: ALCClassObjectFactory) {
        AcEnabledUserDefaults()
    }
}

When you add this, Alchemic will automatically add user defaults support to the model by adding a singleton factory to the model which instantiates an instance of ALCUserDefaults on startup. ALCUserDefaults provides the following features:

Here’s a more complete example of accessing the user defaults:

@implementation MyClass {
    ALCUserDefaults *_defaults;
}

AcEnabledUserDefaults
AcInject(_defaults)

-(void) someMethod {
    NSString *name = _defaults["username"];
    _defaults["username"] = "derek";
}

@end

Setting a value automatically saves it to user defaults.

Custom user defaults

By extending ALCUserDefaults to create your own class you can add properties to match the user defaults. You then declare the class as a singleton in Alchemic’s model. With user defaults enabled, Alchemic will see your custom user defaults in the model and not add it’s own.

When instantiated, the code inside the parent ALCUserDefaults will first load all user defaults into the properties, then locate all the writeable properties and KVO watch them. When you set a new value, the KVO watch will automatically forward it to the user defaults system.

The up shot of this is that a fully Alchemic integrated user defaults with code completable properties, defaults sourced from a Roots.plist file, and backed by [NSUserDefaults standardUserDefaults] is a simple as this:

@interface MyUserDefaults: ALCUserDefaults
    @property (nonatomic, strong) NSString *username;
    @property (nonatomic, assign) int nbrLogins;
@end
@implementation MyUserDefaults
AcEnableUserDefaults
@end
@implementation MyClass {
    MyUserDefaults *_defaults;
}

AcInject(_defaults)

-(void) someMethod {
    NSString *name = _defaults.username;
    _defaults.username = "derek";
}

@end

Cloud based key value storage

Apple provides a cloud base key value storage facility which you can use in your apps. Essentially this works exactly the same was as NSUserDefaults except the data is stored in the cloud rather than on the device. This enables a simple mechanism which can synchronise your data (within certain limits) between your devices.

To access the cloud store you need to switch on the iCloud and Key Value Storage in your apps capabilities like this:

Then you need to enabled Alchemic’s Cloud Key Value Storage feature like this:

@implementation MyClass
AcEnabledCloudKeyValueStore
@end

Alchemic’s cloud key value storage feature works exactly like it’s ALCUserDefaults counterpart and has the same functionality. It’s based around the ALCCloudKeyValueStore class and included the ability to be extended if you want to create a custom class to access the data.

Here’s the basic method for accessing cloud based values:

Here’s a more complete example of accessing the user defaults:

@implementation MyClass {
    ALCCloudKeyValueStore *_ckvs;
}

AcEnableCloudKeyValueStore
AcInject(_ckvs)

-(void) someMethod {
    NSString *name = _defaults["username"];
    _ckvs["username"] = "derek";
}

@end

Custom value transformers

With all key value stores you can store values of types that are not natively supported by their backing stores. To do so, you need to implement two methods for each property which is of an unknown type.

-(id) <property-name>FromBackingStoreValue:(id) value;
-(id) backingStoreValueFrom<property-name>:(id) value;

For example if you want to store instances of MyClass in the cloud, you have to transform those instances into something that the cloud can store. Here’s an example of implementing suitable transformers.

@interface MyCloudStore : ALCCloudKeyValueStore
// Defining a property is optional, but useful.
@property (nonatomic, strong) MyClass *myObject;
@end
@implementation MyCloudStore

-(MyClass *) myObjectFromBackingStoreValue:(NSDictionary *) values {
    MyClass *myClass = [[MyClass alloc] init];
    [MyClass setValuesForKeysWithDictionary:values];
    return myClass;
}

-(NSDictionary *) backingStoreValueFromMyObject:(id) myObj {
    return @[
        // myObj properties.
    ];
}

@end

When sending a value to or from the backing key value store, Alchemic will automatically search for and call these transformer methods if they exist.