魔法と血を用いたObjective-Cでの依存性注入

MVC分離では不十分です



毎日、iOSアプリケーションはより扱いにくくなり、その結果、MVCが1つでは不十分です。



さまざまな目的のためのクラスが増えています。ロジックはサービスから取り出され、モデルはデコレーターに変わり、大きな表現は小さな部分に分割されます。 そして最も重要なことは、この場合、多くの依存関係があり、何らかの方法でそれらを管理する必要があることです。



多くの場合、Singletonは、基本的に誰もがアクセスできるグローバル変数である依存関係の問題を解決するために使用されます。

このコードを見た頻度は?



[[RequestManager sharedInstance] loadResourcesAtPath:@"http://example.com/resources" withDelegate:self]; //  [[DatabaseManager sharedManager] saveResource:resource];
      
      





このアプローチは多くのプロジェクトで使用されていますが、いくつかの欠点があります。





最初の問題は非常に簡単に解決できます-プロパティを使用する必要があります:



 @interface ViewController : UIViewController @property (nonatomic, strong) RequestManager *requestManager; @end
      
      





しかし、このアプローチには他の欠点があります-今、誰かがこのプロパティを「埋める」必要があります。

Blood Magicはこの問題の解決に役立ちます。





依存性注入



これらの問題は、Objective-Cに固有のものではありません。 JavaやC ++などの「産業用」言語をもっと見ると、解決策が見つかります。 Javaで一般的に使用されるアプローチ- 依存性注入(DI)



DIを使用すると、 requestManager



をアプリケーションでシングルトンとして使用できますが、テストではそれをモックに置き換えます。 同時に、 RequestManager



RequestManager



ViewController



について何も知りません。この動作はDIフレームワークを制御するためです。



githubのObjective-Cには多くのDI実装がありますが、欠点があります。







ブラッドマジック



次のフレームワーク(他の欠陥を含む)を見てみましょう-Blood Magic(BloodMagic、BM)



BMは、Objective-Cクラスのプロパティのカスタム属性の類似性を実装します。 スケーラビリティを念頭に置いて設計されており、より多くの機能がまもなく追加されます。 現時点では、 Lazy、Lazy Initializationという 1つの属性のみが実装されています。



この属性を使用すると、ルーチンコードを記述せずに、必要に応じてプロパティを初期化できます。 したがって、同様のシートの代わりに:



 @interface ViewController : UIViewController @property (nonatomic, strong) ProgressViewService *progressViewService; @property (nonatomic, strong) ResourceLoader *resourceLoader; @end @implementation ViewController - (void)loadResources { [self.progressViewService showProgressInView:self.view]; self.resourceLoader.delegate = self; [self.resourceLoader loadResources]; } - (ProgressViewService *)progressViewService { if (_progressViewService == nil) { _progressViewService = [ProgressViewService new]; } return _progressViewService; } - (ResourceLoader *)resourceLoader { if (_resourceLoader == nil) { _resourceLoader = [ResourceLoader new]; } return _resourceLoader; } @end
      
      





あなたは単に書くことができます:



 @interface ViewController : UIViewController <BMLazy> @property (nonatomic, strong, bm_lazy) ProgressViewService *progressViewService; @property (nonatomic, strong, bm_lazy) ResourceLoader *resourceLoader; @end @implementation ViewController @dynamic progressViewService; @dynamic resourceLoader; - (void)loadResources { [self.progressViewService showProgressInView:self.view]; self.resourceLoader.delegate = self; [self.resourceLoader loadResources]; } @end
      
      





そしてそれだけです。 self.progressViewService



self.resourceLoader



初めてself.progressViewService



れたときに、両方の@dynamic



プロパティが作成さself.resourceLoader



ます。 これらのオブジェクトは、 ViewController



解放された後、通常のプロパティと同様に解放されます。



血の魔術と依存注射



デフォルトでは、オブジェクトを作成するために+new



クラスメソッドが使用されます。 ただし、BMの主要な機能である独自のカスタム初期化子をDIフレームワークとして記述することは可能です。



カスタム初期化子の作成は少し冗長です。



 BMInitializer *initializer = [BMInitializer lazyInitializer]; initializer.propertyClass = [ProgressViewService class]; initializer.initializer = ^id (id sender){ return [[ProgressViewService alloc] initWithViewController:sender]; }; [initializer registerInitializer];
      
      





propertyClass



初期化子はこのクラスのプロパティに登録されます。

initializer



-オブジェクトを初期化するために呼び出されるブロック。 このnil



ブロックまたは初期化子が見つからない場合、オブジェクトは+new



メソッドを使用して作成されます。

sender



はコンテナクラスのインスタンスです。



初期化子にはcontainerClass



プロパティもあります。これにより、コンテナに基づいて、さまざまな方法で同じプロパティの作成を記述することができます。 例:



 BMInitializer *usersLoaderInitializer = [BMInitializer lazyInitializer]; usersLoaderInitializer.propertyClass = [ResourceLoader class]; usersLoaderInitializer.containerClass = [UsersViewController class]; usersLoaderInitializer.initializer = ^id (id sender){ return [ResourceLoader usersLoader]; }; [usersLoaderInitializer registerInitializer]; BMInitializer *projectsLoaderInitializer = [BMInitializer lazyInitializer]; projectsLoaderInitializer.propertyClass = [ResourceLoader class]; projectsLoaderInitializer.containerClass = [ProjectsViewController class]; projectsLoaderInitializer.initializer = ^id (id sender){ return [ResourceLoader projectsLoader]; }; [projectsLoaderInitializer registerInitializer];
      
      





したがって、 UsersViewController



ProjectsViewController



に対して異なるオブジェクトが作成されます。 デフォルトでは、 containerClass



NSObject



クラスと同じです。



初期化子は、記事の冒頭で説明したさまざまなshared*



メソッドとハードコードを取り除くのに役立ちます。



 BMInitializer *initializer = [BMInitializer lazyInitializer]; initializer.propertyClass = [RequestManager class]; initializer.initializer = ^id (id sender){ static id singleInstance = nil; static dispatch_once_t once; dispatch_once(&once, ^{ singleInstance = [RequestManager new]; }); return singleInstance; }; [initializer registerInitializer];
      
      





初期化子の構成と保管



プロジェクトには多くの初期化子を含めることができるため、それらを別の場所/モジュールに移動することは理にかなっています。



良い解決策は、それらを異なるファイルに分割し、コンパイラフラグを使用することです。 Blood Magicには、これらの属性を隠す単純なマクロlazy_initializer



ます。 必要なのは、ヘッダーなしでファイルを作成し、それをコンパイルフェーズに追加することだけです。



例:



 // LoaderInitializer.m #import <BloodMagic/Lazy.h> #import "ResourceLoader.h" #import "UsersViewController.h" #import "ProjectsViewController.h" lazy_initializer ResourseLoaderInitializers() { BMInitializer *usersLoaderInitializer = [BMInitializer lazyInitializer]; usersLoaderInitializer.propertyClass = [ResourceLoader class]; usersLoaderInitializer.containerClass = [UsersViewController class]; usersLoaderInitializer.initializer = ^id (id sender){ return [ResourceLoader usersLoader]; }; [usersLoaderInitializer registerInitializer]; BMInitializer *projectsLoaderInitializer = [BMInitializer lazyInitializer]; projectsLoaderInitializer.propertyClass = [ResourceLoader class]; projectsLoaderInitializer.containerClass = [ProjectsViewController class]; projectsLoaderInitializer.initializer = ^id (id sender){ return [ResourceLoader projectsLoader]; }; [projectsLoaderInitializer registerInitializer]; }
      
      





lazy_initializer



__attribute__((constructor)) static void



置き換えられ__attribute__((constructor)) static void



constructor



属性は、このメソッドがmain



よりも早く呼び出されることを意味します( GCC。Function Attributesに詳細な説明があります)。



近い将来の計画






All Articles