Reliant - Nonintrusive Objective-C Dependency Injection

Overview

Reliant

Reliant build status Cocoapods Version

Reliant is a Dependency Injection (DI) framework for Objective-C, both for OS X and iOS. Its goal is to make its use as simple as possible, while not limiting possibilities. It aims to have as little impact as possible on your project code. It also aims to be loyal to Objective-C's dynamic nature.

Getting started

In this section we will get you started with Reliant as quick as possible, if you want to know more (or in other words, the TL;DR version) we suggest you take a look at our wiki pages

Installation

The easiest way to install Reliant is via CocoaPods

Add the following line to your Podfile:

pod 'Reliant'

Then run pod install or pod update

for more information about CocoaPods, go to http://cocoapods.org

Quick-start tutorial

We suggest that you first take a look at our quick-start 'Hello World' tutorial and sample app, found under the Examples folder in the Reliant repository.

Using Reliant

The following documentation is based on our more elaborate sample app, which can also be found under the Examples folder in the Reliant repository.

Configuration

You first need a context in which Reliant will look for your specific objects. The default way to configure such a context is through a configuration class. The example contains some of these. The application wide context is configured with the AppConfiguration class.

//Header file omitted

@implementation AppConfiguration

- (id<StringProvider>)createSingletonStringProvider {
    return [[DefaultStringProvider alloc] init];
}

@end

In this very simple example we have the concept of a StringProvider which will generate some strings shown by various views in our application. We configure Reliant to create a "singleton" instance of this string provider. The reason why you would use dependency injection is that you can avoid hard dependencies to implementations. That's why we have a StringProvider protocol. The configuration will create an actual implementation instance, but that instance is hidden from the actual dependent application code. In this case we use the DefaultStringProvider.

Bootstrapping a context

Bootstrapping a context is very simple. Since we have a configuration for a context which is meant to be used throughout the entire application, we will bootstrap this context in the application delegate.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [self ocsBootstrapAndBindObjectContextWithConfiguratorFromClass:[AppConfiguration class]];
}

This single line of code is the easiest way to bootstrap a context. It wil start the context with the specified configuration class, and then bind that context to self which in this case means your application delegate.

Injection

Reliant injects all your objects specified in the configuration. Injection can be done in two ways:

  • Initializer injection
  • Property injection

For simplicity's sake we will use property injection in these examples.

We actually prefer initializer injection over property injection, but we will get into that in our wiki pages.

Let's say that our DefaultStringProvider implementation needs a StringGenerator to generate some strings. We could do this by simply adding a property named stringGenerator on our DefaultStringProvider.

@interface DefaultStringProvider : NSObject<StringProvider>

@property (nonatomic, strong) id<StringGenerator> stringGenerator;

@end

Now we just need to add another configuration method to our AppConfiguration class.

//Inside the implementation of our AppConfiguration

- (id<StringGenerator>)createSingletonStringGenerator {
    return [[DefaultStringGenerator alloc] init];
}

With that, when you start your application, both the DefaultStringProvider and DefaultStringGenerator are being created for the AppDelegate's context. Remember when we said they were created as "singletons"? Well, they are not real singletons, but they are in the AppDelegate context. When you ask the context for this object, it will always return the same instance, guaranteed.

For those of you who prefer to put the property in an anonymous class extension, as we do, that would work as well.

After the creation of an object, it will be injected with other objects known by the context it is created for. So in this case the DefaultStringGenerator is injected in the DefaultStringProvider through its stringGenerator property.

You easily succeeded in loosly coupling the StringGenerator to your DefaultStringProvider class.

Manual injection

It might not always be possible to configure your objects through Reliant. For instance, a view controller might get created by a storyboard or by your applications code somewhere. In these cases Reliant will not be able to inject your object automatically. However, injecting an object is a one-liner again:

[self ocsInject];

This will locate a context based on self, and then inject selfwith objects known to the found context.

Naming your objects

Reliant figures out which objects to inject by their given name. In this case, the names of our object are stringProvider and stringGenerator. That is why we named the property in DefaultStringProvider as such. The names of the objects are specified by your configurator. In this case Reliant derives the name from the method names. All text which comes after the createSingleton is seen as a name. The tentative reader might argue that the names should be StringGenerator and StringProvider (with starting capital), in fact that is true. However, Reliant has created aliases for these objects in their camelcase form.

Further reading

Do not forget to check our wiki pages for more details on what is discussed above. Our API documentation is available via cocoadocs.org

Contact

If not via GitHub, find us on twitter: @AppFoundryBE or @mikeseghers

Licence

Reliant is released under MIT licence

Comments
  • What about Widgets and Watch?

    What about Widgets and Watch?

    Hello,

    I'm trying to use reliant in my today widget and my watch kit extension, but (even when I call ocsInject) my dependencies aren't injected... Do I also need to call ocsBootstrapAndBindObjectContextWithConfiguratorFromClass somewhere on the widget/watch code itself (since they don't really use the AppDelegate)? If so, where, if not, can you help me to find out the issue..?

    Thx!

    opened by jaernouts 10
  • Named object contexts might fight each other

    Named object contexts might fight each other

    Take the following situation:

    A view controller bootstraps an object context. When an application would the initiate two instances of that view controller, living in memory at the same time (eg a detail view controller in a pager), those controllers will have a context with the same name. The current system (context registry et al) cannot deal with this situation. If other controllers now use the context as parent, reliant will always just retain the last created context. This is unintended behaviour. The whole named context system needs to be reviewed. It probably isn't the best idea.

    bug 
    opened by mikeseghers 6
  • Manager not injected in initialiser in other manager

    Manager not injected in initialiser in other manager

    Hi guys!

    I discovered an issue (well, at least I think it's an issue, maybe I'm doing something wrong), when you use a manager within another manager. I'll show you a small example:

    I have a 'UserManager' which uses a 'PreferencesManager' in his init method. As the documentation says, the injection should be done automatically, but my PreferencesManager keeps being 'nil'. When I do '[self ocsInject]', it's working correctly - but I thought I didn't have to do that?

    So, wrong, not working - self.preferencesManger is still nil:

    - (instancetype)init {
        self = [super init];
        if (self) {
            _primaryMsisdn = [self.preferencesManager objectForKey:PreferenceKeyPrimaryMsisdn];
        }
    
        return self;
    }
    

    Good, self.preferencesManager is injected properly:

    - (instancetype)init {
        self = [super init];
        if (self) {
            [self ocsInject];
    
            _primaryMsisdn = [self.preferencesManager objectForKey:PreferenceKeyPrimaryMsisdn];
        }
    
        return self;
    }
    

    It's a good workaround for now, but I thought I didn't need to do the '[self ocsInject]' within already injected managers...?

    Regards!

    opened by jaernouts 5
  • Use steipete/Aspects to auto inject properties non-intrusively

    Use steipete/Aspects to auto inject properties non-intrusively

    Not sure if this has been done before, but I tested out using @steipete's Aspects to set up automatic property injection without imposing boilerplate [self ocsInject]s all over the place (and it works rather nicely). It also furthers our goal of being non-intrusive - regardless of view life cycle particulars.

    E.g.: set up a hook in the -viewDidLoad method of any UIViewController which calls -ocsInject before control is handed back to the original -viewDidLoad implementation.

    NSError *error;
    [UIViewController aspect_hookSelector:@selector(viewDidLoad)
                              withOptions:AspectPositionBefore
                               usingBlock:^(id <AspectInfo> aspectInfo) {
                                   UIViewController *instance = (UIViewController *) aspectInfo.instance;
                                   [instance ocsInject];
                               }
                                    error:&error];
    

    I think we could (optionally) make this part of the the root bootstrapping process. I'm working on an example to demonstrate this.

    Open to suggestions, improvements and/or critique.

    enhancement 
    opened by aceontech 4
  • Suggestion: Use class instead of string for parentContext reference?

    Suggestion: Use class instead of string for parentContext reference?

    See OCSConfiguratorFromClass.h:27

    In order to limit typos, could we change this to -parentContextClass instead? The implementation could still derive a string using NSStringFromClass.

    enhancement 
    opened by aceontech 3
  • Watch OS 2 support

    Watch OS 2 support

    Hi Guys! I see that your pod spec doesn't include watchOS 2 as supported platform... This way I have problems implementing it in my watch app... Can you take a look?

    Thanks!

    opened by jaernouts 1
  • Feature/bart/sample app

    Feature/bart/sample app

    Very simple HelloWorld sample app and tutorial.

    important: Don't forget that the 'main' README.md does not reference this sample app yet. It still references the now removed Example folder and project therein.

    opened by bartvandeweerdt 0
  • Storyboards loading sequence

    Storyboards loading sequence

    When you define a storyboard to be loaded in the project settings it can occur that injection will take place before the reliant context is loaded.

    Solution: A Solution is to manually load the storyboard after you load the context in the applicationDidFinishLaunchingWithOptions: method

    this should be added to the documentation

    to document 
    opened by Goesman 0
  • Reliant vs optional protocol properties

    Reliant vs optional protocol properties

    When you perform an injection on a class that implements an protocol with an optional property, reliant forces me to implement it. If I don't, the application crashes, complaining about key-value compliance. Implementing the property fixes the issue, but this is obviously just a work-around.

    bug 
    opened by Goesman 0
  • Code in charge of bootstrapping contexts is not testable

    Code in charge of bootstrapping contexts is not testable

    In our examples and documentation we suggest bootstrapping context in the form of:

    [someObject ocsBootstrapAndBindObjectContextWithConfiguratorFromClass:[SomeContextConfiguration class]];
    [someObject ocsInject];
    

    While this code avoids a lot of boilerplate, it makes the class responsible for bootstrapping less testable. We need some indirection to make the code independent of the NSObject categories.

    opened by mikeseghers 0
  • [WIP]: Reliant/Swift: Context substitution

    [WIP]: Reliant/Swift: Context substitution

    ⚠️ Work-in-progress, do not merge ⚠️

    Exploring a way to register context substitutions so they can be swapped out for testing or other purposes.

    Current implementation

    I added a map to the context cache which will be accessible through relyOnSubstitute(T)(T). This global function simply creates an entry in the map, which relyOn() will then use to determine if it needs to create a substitute context or a normal one, when requested.

    Remarks

    I have only gotten this to work half-way. The substitute type is registered and found correctly, but when its createContext() function is invoked, the original, non-substituted version is called because createContext() is static with no way to override it, because static also means final in Swift.

    Because of how I set up the relyOnSubstitute's arguments (both of the same type T:ReliantContext), the second argument needs to resolve to the same type as the first. That is, the second argument needs to be covariant with the first. This is currently achieved by subclassing the original context, with currently no way to modify createContext()'s implementation due to the reason stated above.

    Subclassing doesn't feel right, so I'm currently exploring ways to change relyOnSubstitutes signature to something like:

    public func relyOnSubstitute<T:ReliantContext, S:ReliantContext>(type:T.Type)(_ otherType:S.Type)
    

    This causes serious type resolution problems of course, so I'm still trying to figure something out.

    opened by aceontech 0
  • Class based configuration does not work well when using inheritance

    Class based configuration does not work well when using inheritance

    When configuration class B extends from configuration class A, only create methods from class B are being picked up. The work around for now is letting class B override all methods in class A. This is a lot of boilerplate, which reliant could easily avoid.

    opened by mikeseghers 0
Owner
AppFoundry
AppFoundry
Cleanse is a dependency injection framework for Swift.

Cleanse - Swift Dependency Injection Cleanse is a dependency injection framework for Swift. It is designed from the ground-up with developer experienc

Square 1.7k Sep 20, 2022
Corridor A Coreader-like Dependency Injection μFramework

Corridor A Coreader-like Dependency Injection μFramework Table of Contents Why | Examples | Usage | Installation | Credits & License | Why In order to

symentis GmbH 58 Jan 13, 2022
Deli is an easy-to-use Dependency Injection Container that creates DI containers

Deli is an easy-to-use Dependency Injection Container that creates DI containers with all required registrations and corresponding factories.

Jungwon An 134 Aug 10, 2022
DIKit Dependency Injection Framework for Swift, inspired by KOIN.

DIKit Dependency Injection Framework for Swift, inspired by KOIN. Basically an implementation of service-locator pattern, living within the applicatio

null 97 Sep 11, 2022
Dip is a simple Dependency Injection Container.

Dip is a simple Dependency Injection Container. It's aimed to be as simple as possible yet p

Olivier Halligon 945 Sep 13, 2022
Tranquillity is a lightweight but powerful dependency injection library for swift.

DITranquillity Tranquillity is a lightweight but powerful dependency injection library for swift. The name "Tranquillity" laid the foundation in the b

Ivlev Alexander 391 Sep 6, 2022
Swinject is a lightweight dependency injection framework for Swift.

Swinject Swinject is a lightweight dependency injection framework for Swift. Dependency injection (DI) is a software design pattern that implements In

null 5.4k Sep 23, 2022
Typhoon Powerful dependency injection for Cocoa and CocoaTouch.

Typhoon Powerful dependency injection for Cocoa and CocoaTouch. Lightweight, yet full-featured and super-easy to use. Pilgrim is a pure Swift successo

AppsQuick.ly 2.7k Sep 16, 2022
Dependency Injection framework for Swift (iOS/macOS/Linux)

Declarative, easy-to-use and safe Dependency Injection framework for Swift (iOS/macOS/Linux) Features Dependency declaration via property wrappers or

Scribd 668 Sep 10, 2022
Swift Ultralight Dependency Injection / Service Locator framework

Swift Ultralight Dependency Injection / Service Locator framework

Michael Long 1.9k Sep 19, 2022
DIContainer Swift is an ultra-light dependency injection container made to help developers to handle dependencies easily. It works with Swift 5.1 or above.

?? DIContainer Swift It is an ultra-light dependency injection container made to help developers to handle dependencies easily. We know that handle wi

Victor Carvalho Tavernari 8 May 31, 2022
A simple way to handle dependency injection using property wrappers

Injektion Introduction A simple way to handle dependency injection using propert

Andrew McGee 2 May 31, 2022
Kraken - Simple Dependency Injection container for Swift. Use protocols to resolve dependencies with easy-to-use syntax!

Kraken Photo courtesy of www.krakenstudios.blogspot.com Introduction Kraken is a simple Dependency Injection Container. It's aimed to be as simple as

Syed Sabir Salman-Al-Musawi 1 Oct 9, 2020
Pilgrim - Dependency injection for Swift (iOS, OSX, Linux). Strongly typed, pure Swift successor to Typhoon.

pilgrim.ph Pilgrim is a dependency injection library for Swift with the following features: Minimal runtime-only library that works with pure Swift (s

AppsQuick.ly 59 Sep 11, 2022
Perform - Easy dependency injection for storyboard segues

Perform Easy dependency injection for storyboard segues. import Perform // ... func tableView(_ tableView: UITableView, didSelectRowAt indexPath: NS

thoughtbot, inc. 280 Feb 6, 2022
StoryboardBuilder - Simple dependency injection for generating views from storyboard.

StoryboardBuilder Simple dependency injection for generating views from storyboard. Description StoryboardBuilder is framework to help simply and easi

null 5 Jun 13, 2019
ViperServices - Simple dependency injection container for services written for iOS in swift supporting boot order

ViperServices Introduction ViperServices is dependency injection container for iOS applications written in Swift. It is more lightweight and simple in

Siarhei Ladzeika 5 Jan 9, 2022
Needle - Compile-time safe Swift dependency injection framework

Needle is a dependency injection (DI) system for Swift. Unlike other DI frameworks, such as Cleanse, Swinject, Needle encourages hierarchical DI struc

Uber Open Source 1.4k Sep 20, 2022
Injector - A Swift package for simple dependency injection that also supports Swift UI previews

A Swift package for simple dependency injection that also supports Swift UI prev

null 6 Aug 9, 2022