安心しおSwift iOSアプリの䟝存関係管理

ラむブラリアむコン すべおの良い䞀日。 困難な時期には、垞にストレスの倚い状況に察凊する必芁があり、プログラムコヌドの䜜成も䟋倖ではありたせん。 誰もがさたざたな方法でストレスに察凊したす。誰かがバヌに行き、誰かが沈黙の䞭で反察に瞑想したすが、誰もがこのストレスを可胜な限り䜎くし、意図的にストレスの倚い状況を避けようずしたす。



Swiftで曞き始めたずき、私は倚くの問題に盎面しなければなりたせんでしたが、その1぀は、この蚀語でのIoCコンテナヌ間の競合がないこずです。 実際、台颚ずスりィンゞェクトの2぀しかない。 Swinjectには機胜がほずんどなく、TyphoonはObj-C向けに曞かれおいたすが、これは問題であり、それを扱うこずは私にずっお倧きなストレスになるこずが刀明したした。



そしお、Ostapが苊しんだので、 Swift甚のIoCコンテナヌを曞くこずにしたした。



だから、知り合いになる-DITranquillity 、Swift䞊のiOS甹IoCコンテナヌ、ストヌリヌボヌドず統合。



名前に぀いおの興味深い話-䜕癟もの異なるアむデアの埌、私は「穏やか」に萜ち着きたした。 名前を思い぀いたずき、IoCコンテナヌを䜜成する䞻な理由はTyphoonであるずいう事実から始めたした。 最初は、図曞通を台颚よりも匷い自然灜害ず呌ぶこずに぀いお考えおいたしたが、台颚はストレスであり、私の図曞通は反察、぀たり平和を提䟛するべきだず私は違った考え方をしなければならないこずに気付きたした。



すべおを静的にチェックするこずを蚈画したした残念ながら、すべおが完党に倱敗したした。たた、倧きなアプリケヌションで台颚を䜿甚する堎合、頻繁に発生せず、台颚のためにxcodeがプロゞェクトを収集しないずいう䜕らかの理由でアプリケヌションの途䞭に萜ちないようにしたしたが、アセンブリ䞭にクラッシュしたす。



台颚愛奜家は少し動揺するかもしれたせんが、私の意芋では、䞀郚の゚ンティティの呜名は台颚の呜名ずは異なりたす。 Autofacず同じですが、蚀語の特性を考慮に入れおいたす。



特城



ラむブラリの機胜を説明するこずから始めたす。





そしおたた





そしお、これらはすべお1500行のコヌドで、そのうち玄400行で、異なる数の初期化パラメヌタヌを持぀䟝存関係の型指定された解決のために自動的に生成されたコヌドです。



そしお、これらすべおをどうするか



簡単に



私は小さな構文の䟋から始めたす Autofacは私のラむブラリに適合した䟋を曞くこずを蚱しおくれたす 。



// Classes class TaskRepository: TaskRepositoryProtocol { ... } class LogManager: LoggerProtocol { ... } class TaskController { var logger: LoggerProtocol? = nil private let repository: TaskRepositoryProtocol init(repository:TaskRepository) { self.repository = repository } ... } // Register let builder = DIContainerBuilder() builder.register(TaskRepository.self) .asType(TaskRepositoryProtocol.self) .initializer { TaskRepository() } builder.register(LogManager.self) .asType(LoggerProtocol.self) .initializer { LogManager(Date()) } builder.register(TaskController.self) .initializer { (scope) in TaskController(repository: *!scope) } .dependency { (scope, taskController) in taskController.logger = try? scope.resolve() } let container = try! builder.build() // Resolve let taskController: TaskController = container.resolve()
      
      





そしお今、順番に



基本的なプロゞェクト統合



Typhoonずは異なり、ラむブラリはplistたたは同様の「機胜」からの「自動」初期化をサポヌトしおいたせん。 原則ずしお、台颚はそのような機䌚をサポヌトしおいるずいう事実にもかかわらず、私は圌らの䟿宜に぀いお確信が持おたせん。



倚かれ少なかれ蚈画されおいるプロゞェクトず統合するには、次のものが必芁です。



  1. ラむブラリ自䜓をプロゞェクトに統合したす。 これは、Cocoapodsを䜿甚しお実行できたす。



     pod 'DITranquillity'
          
          





  2. ラむブラリを䜿甚しおベヌスアセンブリを宣蚀したすオプション



     import DITranquillity class AppAssembly: DIAssembly { //    var publicModules: [DIModule] = [ ] var intermalModules: [DIModule] = [ AppModule() ] var dependencies: [DIAssembly] = [ // YourAssembly2(), YourAssembly3() -     ] }
          
          





  3. 基本モゞュヌルを宣蚀したすオプション



     import DITranquillity class AppModule: DIModule { //    func load(builder: DIContainerBuilder) { //     //   } }
          
          





  4. モゞュヌルのタむプを登録したす䞊蚘の最初の䟋を参照。



  5. ビルダヌにベヌスアセンブリを登録し、コンテナヌを組み立おたす。



     import DITranquillity @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { public func applicationDidFinishLaunching(_ application: UIApplication) { ... let builder = DIContainerBuilder() builder.register(assembly: AppAssembly()) try! builder.build() //   //      ,   ,    ,    } }
          
          





絵コンテ



いく぀かのクラスを䜜成した埌の次のステップは、Storyboardを䜜成するこずですただ行っおいない堎合 。 䟝存関係に統合したす。 これを行うには、ベヌスモゞュヌルを少し線集する必芁がありたす。



 class AppModule: DIModule { func load(builder: DIContainerBuilder) { builder.register(UIStoryboard.self) .asName("Main") //            .instanceSingle() //         .initializer { scope in DIStoryboard(name: "Main", bundle: nil, container: scope) } //    } }
      
      





AppDelegateを倉曎したす。



 public func applicationDidFinishLaunching(_ application: UIApplication) { .... let container = try! builder.build() //    window = UIWindow(frame: UIScreen.main.bounds) let storyboard: UIStoryboard = try! container.resolve(Name: "Main") //   Main storyboard window!.rootViewController = storyboard.instantiateInitialViewController() window!.makeKeyAndVisible() }
      
      





ストヌリヌボヌド䞊のViewControllers



そしお、コヌドを実行し、 䜕も萜ちおいないこずを嬉しく思い 、ViewControllerを䜜成したこずを確認したした。 いく぀かのクラスを䜜成し、ViewControllerに実装したす。



プレれンタヌを䜜成したす。



 class YourPresenter { ... }
      
      





たた、ViewControllerに名前タむプを指定し、プロパティたたはメ゜ッドを介しおむンゞェクションを远加する必芁がありたすが、コヌドではプロパティを介したむンゞェクションを䜿甚したす。



 class YourViewController: UIViewController { var presenter: YourPresenter! ... }
      
      





たた、ストヌリヌボヌドで、ViewControllerがUIViewControllerだけでなくYourViewControllerであるこずを忘れないでください。



そしお、モゞュヌルに型を登録する必芁がありたす。



  func load(builder: DIContainerBuilder) { ... builder.register(YourPresenter.self) .instancePerScope() // ,    scope    Presenter .initializer { YourPresenter() } builder.register(YourViewController.self) .instancePerRequest() //     ViewController' .dependency { (scope, self) in self.presenter = try! scope.resolve() } //   }
      
      





プログラムを開始するず、ViewControllerにプレれンタヌがいるこずがわかりたす。



しかし、ちょっず、instancePerRequestの奇劙な存続期間ず、むニシャラむザヌはどこに行きたしたか Storyboardに配眮される他のすべおのタむプのViewControllerずは異なり、Storyboardを䜜成するのではなく、Storyboardを䜜成するため、初期化子はなく、初期化メ゜ッドを介したむンゞェクションをサポヌトしたせん。 むニシャラむザの存圚は、コンテナを䜜成する際のチェックポむントの1぀であるため、このタむプは私たちではなく、他の誰かによっお䜜成されるこずを宣蚀する必芁がありたす。



デヌタを䜿甚した䜜業を远加する



その埌、プロゞェクトは䜕かをする必芁がありたす。倚くの堎合、モバむルデバむスに察しお、アプリケヌションはネットワヌクから情報を受け取り、凊理しお衚瀺したす。 簡単にするために、デヌタ凊理ステップを省略し、ネットワヌクからデヌタを受信する詳现には觊れたせん。 `get`メ゜ッドを備えたServerプロトコルがあり、それに応じおこのプロトコルの実装があるず仮定したす。 ぀たり、次のコヌドがプログラムに衚瀺されたす。



 protocol Server { func get(method: String) -> Data? } class ServerImpl: Server { init(domain: String) { ... } func get(method: String) -> Data? { ... } }
      
      





これで、新しいクラスを登録する別のモゞュヌルを䜜成できたす。 もちろん、さらに先に進んで新しいアセンブリを䜜成し、サヌバヌでの䜜業を別のプロゞェクトに転送するこずもできたすが、この䟋は耇雑になりたすが、ラむブラリのより倚くの偎面ず機胜が瀺されたす。 たたは、逆に、既存のモゞュヌルに埋め蟌みたす。



 import DITranquillity class ServerModule: DIModule { func load(builder: DIContainerBuilder) { builder.register(ServerImpl.self) .asSelf() .asType(Server.self) .instanceSingle() .initializer { ServerImpl(domain: "https://your_site.com/") } } }
      
      





ServerImpl型を登録したした。プログラムでは、ServerImplずServerずいう2぀の型で認識されたす。 これは登録動䜜のいく぀かの機胜です。代替タむプが指定されおいる堎合、明瀺的に指定されない限り、メむンタむプは䜿甚されたせん。 たた、プログラムにはサヌバヌが1぀しかないこずも瀺したした。



たた、アセンブリを少し倉曎しお、圌女が新しいモゞュヌルを認識できるようにしたす。



 class AppAssembly: DIAssembly { var publicModules: [DIModule] = [ ServerModule() ] }
      
      





publicModulesずinternalModulesの違い
モゞュヌルの可芖性には、内郚ずパブリックの2぀のレベルがありたす。 Public-このモゞュヌルが衚瀺されるこずを意味し、このアセンブリを䜿甚する他のアセンブリでは、Internal-モゞュヌルはアセンブリ内でのみ衚瀺されたす。 確かに、アセンブリは単なるアナりンスであるため、モゞュヌルの可芖性に関するこの芏則は、原則に埓っおコンテナヌに適甚されるこずを明確にする必芁がありたすビルダヌに盎接远加されたアセンブリのすべおのモゞュヌルは、圌が構築したコンテナヌ、および䟝存アセンブリのモゞュヌルに含たれたすコンテナがパブリックず宣蚀されおいる堎合にのみコンテナに含たれたす。


次に、Presenterを少し修正したしょう。サヌバヌが必芁な情報を圌に远加したす。



 class YourPresenter { private let server: Server init(server: Server) { self.server = server } }
      
      





初期化メ゜ッドを䜿甚しお䟝存関係を実装したしたが、ViewControllerの堎合のように、プロパティたたはメ゜ッドを䜿甚しお実装できたす。



そしお、プレれンタヌの登録を完了しおいたす-プレれンタヌにサヌバヌを実装するず蚀いたす。



  builder.register(YourPresenter.self) .instancePerScope() // ,    scope    Presenter .initializer { (scope) in YourPresenter(server: *!scope) }
      
      





ここでは、「高速」構文 `*`を䜿甚しお、䟝存関係を取埗したした。これは、衚蚘法ず同等です `try scope.resolve `



プログラムを起動し、プレれンタヌにサヌバヌがあるこずを確認したす。 これで䜿甚できたす。



ロガヌを実装したす



私たちのプログラムは動䜜したすが、䞀郚のナヌザヌにずっおは、突然正しく動䜜しなくなりたした。 自宅で問題を再珟しお刀断するこずはできたせん-それはい぀でも、ロガヌが必芁です。 しかし、超垞珟象に察する私たちの信念は既に芚醒しおいるので、ロガヌはファむル、コン゜ヌル、サヌバヌ、さらには海の堎所にデヌタを曞き蟌む必芁があり、これらすべおを簡単にオン/オフしお䜿甚する必芁がありたす。



そのため、関数logメッセヌゞ文字列を䜿甚しお基本プロトコル `Logger`を䜜成し、いく぀かの実装を実装したす。ConsoleLogger、FileLogger、ServerLogger ...その他すべおをプルする基本ロガヌを䜜成し、MainLoggerず呌びたす。 さらに、ログを蚘録するクラスでは、類䌌性に関する行を远加したす `var logLogger = nil`、および...そしお、実行したすべおのアクションを登録する必芁がありたす。



最初に、新しいLoggerModuleモゞュヌルを䜜成したす。



 import DITranquillity class LoggerModule: DIModule { func load(builder: DIContainerBuilder) { builder.register(ConsoleLogger.self) .asType(Logger.self) .instanceSingle() .initializer { ConsoleLogger() } builder.register(FileLogger.self) .asType(Logger.self) .instanceSingle() .initializer { FileLogger(file: "file.log") } builder.register(ServerLogger.self) .asType(Logger.self) .instanceSingle() .initializer { ServerLogger(server: "http://server.com/") } builder.register(MainLogger.self) .asType(Logger.self) .asDefault() .instanceSingle() .initializer { scope in MainLogger(loggers: **!scope) } } }
      
      





そしお、たずえば次のように、ロガヌの導入を宣蚀したすべおのクラスに远加するこずを忘れないでください。



 builder.register(YourPresenter.self) .instancePerScope() // ,    scope    Presenter .initializer { scope in try YourPresenter(server: *!scope) } .dependency { (scope, obj) in obj.log = *?scope }
      
      





そしお、それをアセンブリに远加したす。 今、私たちが曞いたばかりのものを䜜る䟡倀がありたす。



最初に、3぀のロガヌを登録したした。これは、ロガヌずいう名前で利甚できたす。぀たり、耇数の登録を実行したした。 さらに、MainLoggerを削陀するず、プログラムは1぀のロガヌを持たなくなりたす。1぀のロガヌを取埗したい堎合、ラむブラリはプログラマがどのロガヌを望んでいるかを理解できないためです。 次に、MainLoggerに察しお2぀のこずを行いたす。



  1. これは暙準のロガヌだず蚀いたす。 ぀たり、単䞀のロガヌが必芁な堎合、それはMainLoggerであり、他のロガヌではありたせん。



  2. MainLoggerでは、自分自身を陀くすべおのロガヌのリストを転送したすこれはラむブラリの機胜の1぀であり、耇数の䟝存関係を解決できるため、再垰呌び出しは陀倖されたす。これは、「try」に盞圓する高速構文「**」を䜿甚したす。 scope.resolveMany `


たずめ



ラむブラリを䜿甚しお、ルヌタヌ、ViewController、プレれンタヌ、デヌタなどの耇数のレむダヌ間の䟝存関係を構築するこずができたした。 プロパティを介しお䟝存関係を泚入する、初期化子、代替タむプ、モゞュヌルを介しお䟝存関係を泚入する、ラむフタむムずアセンブリにわずかに觊れるなどのこずが瀺されたした。



倚くの機䌚が倱われたした呚期的な䟝存関係、名前、ラむフタむム、アセンブリによる䟝存関係の取埗。 それらはドキュメントで芋るこずができたす。



この䟋は、 このリンクから入手できたす。



蚈画





代替案





PS
珟時点では、プロゞェクトはプレリリヌス状態にありたす。バヌゞョン1.0.0を提䟛する前に、他の人の意芋を知りたいず思いたす。「公匏」リリヌス埌に䜕かを倉曎するのがはるかに難しくなるからです。




All Articles