LoginSignup
9
8

More than 5 years have passed since last update.

BTKInjector : DI Containerを作ってみた。

Last updated at Posted at 2014-04-22

ここしばらくTyphoon Frameworkを評価していたのですが、結局自前のライブラリを作ることにしました。だってかゆい所に手か届かないんだもの。。。

BTKInjector on github
Cocoa Podsにも登録申請中なので、うまくいけば公開されるはず。

特徴

シンプル

Protocolに対して、その具象クラスを取得するためのProviderかFactoryを登録する機能しかありません。Providerは常に一つのインスタンスを返し、Factoryは常に新しいインスタンスを返します。

DIというとGoogle Guiceのような多機能なものを考えてしまうのですが、クライアントアプリ開発に必要な機能は限られています。BTKInjectorは余計な物を削ぎ落として、コンポーネント間をProtocol経由で粗結合することだけに特化しています。

クリーン

仕様がシンプルなので、実装も非常にシンプルです。
Associated Objectや、Method Swizzlingのようなトリッキーなコードは一切無いので、他のコードに影響を与えることなく導入できるはずです。

使い方

Provider記述方法

プロトコルに対して、対応するインスタンスをどのように生成するかをブロックで登録します。オブジェクト生成時にinjectorを参照できるので、他のプロトコルへの依存関係を注入できます。注入方法はProvider、Factory、Instanceの三種類です。

[mInjector bindProtocol:@protocol(BTKTestProtocol1)
                    toProviderBlock:^id(id<BTKInjector> i) {
                        BTKTestProtocol1Impl *o = [BTKTestProtocol1Impl new];
                        o.protocol2 = [i instanceForProtocol:@protocol(BTKTestProtocol2)];
                        return o;
                    }];

Factory記述方法

Factoryはインタフェイスを個別に書く必要があるのでちょっと面倒です。
詳しくは公式ドキュメントを読んでください。
https://github.com/tomohisaota/BTKInjector

循環参照

BTKInjectorはオブジェクト生成時に依存関係を解決するので、循環した依存関係がある場合にはインスタンス注入に失敗し例外が発生します。もし初期化処理の中でその依存関係を使用しないのであれば、Providerを注入すれば問題なく動作します。

インスタンス注入による循環参照(例外発生)

id<BTKInjector> injector = [BTKGlobalInjector injectorWithBlock:^(id<BTKMutableInjector> mInjector) {
            [mInjector bindProtocol:@protocol(BTKTestProtocol1)
                    toProviderBlock:^id(id<BTKInjector> i) {
                        BTKTestProtocol1Impl *o = [BTKTestProtocol1Impl new];
                        o.protocol2 = [i instanceForProtocol:@protocol(BTKTestProtocol2)];
                        return o;
                    }];
            [mInjector bindProtocol:@protocol(BTKTestProtocol2)
                    toProviderBlock:^id(id<BTKInjector> i) {
                        BTKTestProtocol2Impl *o = [BTKTestProtocol2Impl new];
                        o.protocol3 = [i instanceForProtocol:@protocol(BTKTestProtocol3)];
                        return o;
                    }];
            [mInjector bindProtocol:@protocol(BTKTestProtocol3)
                    toProviderBlock:^id(id<BTKInjector> i) {
                        BTKTestProtocol3Impl *o = [BTKTestProtocol3Impl new];
                        o.protocol1 = [i instanceForProtocol:@protocol(BTKTestProtocol1)];
                        return o;
                    }];
        }];

Provider注入による循環参照(問題なし)

    id<BTKInjector> injector = [BTKGlobalInjector injectorWithBlock:^(id<BTKMutableInjector> mInjector) {
        [mInjector bindProtocol:@protocol(BTKTestProtocol1)
                toProviderBlock:^id(id<BTKInjector> i) {
                    BTKTestProtocol1Impl *o = [BTKTestProtocol1Impl new];
                    o.protocol2Provider = [i providerForProtocol:@protocol(BTKTestProtocol2)];
                    return o;
                }];
        [mInjector bindProtocol:@protocol(BTKTestProtocol2)
                toProviderBlock:^id(id<BTKInjector> i) {
                    BTKTestProtocol2Impl *o = [BTKTestProtocol2Impl new];
                    o.protocol3Provider = [i providerForProtocol:@protocol(BTKTestProtocol3)];
                    return o;
                }];
        [mInjector bindProtocol:@protocol(BTKTestProtocol3)
                toProviderBlock:^id(id<BTKInjector> i) {
                    BTKTestProtocol3Impl *o = [BTKTestProtocol3Impl new];
                    o.protocol1Provider = [i providerForProtocol:@protocol(BTKTestProtocol1)];
                    return o;
                }];
    }];
9
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
8