MVC分離では不十分です
毎日、iOSアプリケーションはより扱いにくくなり、その結果、MVCが1つでは不十分です。
さまざまな目的のためのクラスが増えています。ロジックはサービスから取り出され、モデルはデコレーターに変わり、大きな表現は小さな部分に分割されます。 そして最も重要なことは、この場合、多くの依存関係があり、何らかの方法でそれらを管理する必要があることです。
多くの場合、Singletonは、基本的に誰もがアクセスできるグローバル変数である依存関係の問題を解決するために使用されます。
このコードを見た頻度は?
[[RequestManager sharedInstance] loadResourcesAtPath:@"http://example.com/resources" withDelegate:self]; // [[DatabaseManager sharedManager] saveResource:resource];
このアプローチは多くのプロジェクトで使用されていますが、いくつかの欠点があります。
- テストされたクラス内で使用されるシングルトンは、モックオブジェクトに置き換えるのが難しい
- 本質的にシングルトンはグローバル変数です
- SRPの観点では 、オブジェクトはシングルトンの動作を制御するべきではありません
最初の問題は非常に簡単に解決できます-プロパティを使用する必要があります:
@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実装がありますが、欠点があります。
- マクロまたは文字列定数を使用した依存関係の説明
- 実装は、オブジェクトが「特別な」方法で作成された場合にのみ発生します(このオプションは、ストーリーボードなどから作成された
ViewController
およびView
では機能しません) - 実装されたクラスは、何らかのプロトコルを実装する必要があります(サードパーティまたは標準のライブラリでは機能しません)
- 初期化は別のモジュールに移動できません
- XML
ブラッドマジック
次のフレームワーク(他の欠陥を含む)を見てみましょう-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に詳細な説明があります)。
近い将来の計画
- プロトコルサポートの実装(
@property (nonatomic, strong) id loader)
作業と実装の説明を追加する
新しい属性の追加について説明する
属性を追加します