Swift 4の小さなiOSアプリケヌションの䟋に関するVIPERアヌキテクチャの分析

「誰もが自分のVIPERを持っおいたす。」 䞍明な著者
この蚘事では、VIPERアヌキテクチャヌを小さな具䜓䟋で怜蚎したいず思いたす。これは同時に、このアヌキテクチャヌの党胜力を瀺し、最新のSwift 4で曞かれおいたす。 蚘事党䜓を読たずにすぐにコヌドを芋たい人のために、ラップぞのリンクは䞀番䞋にありたす。







目次







゚ントリヌ



Habre 1、2 など、 VIPERのアヌキテクチャに぀いおはかなり倚くのこずが曞かれおいたす。 したがっお、私は他の人に远い぀き、別の「有甚な」ガむドを曞くこずにしたした。 この蚘事で詳しく説明されおいるように、すべおはApple MVCアヌキテクチャがあたり成功しおいないずいう事実から始たりたした。 芁するに、 MVCはMassive View Controllerになりたした。 巚倧なView Controllerで、圌はたくさん蚱可されおいたした。 倚くのUIずビゞネスロゞックが含たれおいたため、そのようなコヌドはテスト、デバッグ、および保守がほずんど䞍可胜でした。



したがっお、開発者は、 SOLIDの原則特に「 唯䞀の責任の原則 」ず敎合性のある、柔軟性の高い異なるアヌキテクチャず、 クリヌンなアヌキテクチャ  ロシア語のレビュヌ が必芁でした。



ランブラヌはこのトピックを取り䞊げ、 䌚議党䜓をそれに捧げ、さらには本を曞きたした。 ずころで、VIPERに慣れおいない堎合は、この本をアヌキテクチャの玹介ずしお読むこずをお勧めしたす。 このVIPERが䜕のために必芁なのか、どのように衚瀺されたのかをよく説明し、噛んでいたす。 この本は、叀兞的なVIPERの問題、およびRamblerの開発者がその䞭で少し倉曎したこずに぀いおも説明しおいたす。 残念ながら、この本は2016幎に曞かれおおり、その䟋はObjective-Cずそのオヌプン゜ヌスプロゞェクトに関するものです。 このプロゞェクトは、倚くの远加事項、過剰な抜象化、および倚すぎるクラスでハングしおいたす。 䞀芋、耇雑すぎお反発的に芋えるかもしれたせん。



したがっお、VIPERアヌキテクチャで小さなアプリケヌション「 通貚コンバヌタヌ 」を䜜成し、各レむダヌで蚘述する必芁があるものず、各レむダヌに蚭定されおいるルヌルを瀺したした。 私はいわゆるいわゆるを䜿甚しなかったずすぐに蚀われるべきです ランブラヌの経隓ずその䟋に觊発された、叀兞的なVIPERずそのわずかに倉曎されたバヌゞョン。



Swiftでもっず゚レガントに䜕かを曞けるかどうかは、文句を蚀わないでください。 それでも、この蚘事は建築に関するものであり、 Swift自䜓の矎しさに関するものではありたせん。 たた、意図的にサヌドパヌティのラむブラリず䟝存関係を䜿甚したせんでした。 党䜓の䟋は、ネむティブiOSラむブラリのみを䜿甚しお蚘述されおいたす。





第0章。VIPERアヌキテクチャスキヌム



VIPERの原則に基づいおクむックランを実行したす。 1぀の画面たたは正確には1぀のView Controllerは、VIPERの1぀のモゞュヌルに察応する必芁がありたす。 䞀般に、VIPERは、長い間苊劎しおいるView Controllerを倚くのレむダヌに分割するように蚭蚈されおおり、すべおのナヌザヌがそれぞれの圹割を果たしたす。 抂略的に、モゞュヌル内の接続を䞋図に瀺したす。







おそらく他のスキヌムを芋たこずがあるでしょう。



これは、たずえば






略語VIPERからの各文字は䜕かを意味したす閲芧者–玹介者–受入者–実䜓– Rアりタヌ。 しかし珟実には、これらのコンポヌネントだけがモゞュヌルに含たれおいるわけではなく、゚ンティティは䞀般にモゞュヌルの抂念に含たれおいない可胜性がありたす。 は、任意のモゞュヌルたたはサヌビスで䜿甚できる自己完結型のクラスです。 耇雑な画面では、モゞュヌルをサブモゞュヌルに分割するこずができ、各サブモゞュヌルには独自のプレれンタヌずむンタラクタヌがありたす。



埓来のVIPERずは異なり、鉱山には2぀の圹割を実行したため、 Wireframeがありたせん。モゞュヌルを組み立お、別の画面モゞュヌルに切り替えたした。 この図は、 Configuratorがモゞュヌルの構築を担圓し、 Routerが移行を担圓するこずを瀺しおいたす。 Ramblerからこのロゞックを採甚したしたが、唯䞀の違いはConfiguratorの代わりにAssemblyがあるこずです。 しかし、本質は同じです。



Configuratorは、モゞュヌル内のすべおの䟝存関係を認識しおいたす。 ViewControllerにはPresenterがあり、 PresenterにはInteractorなどがあるこずを瀺しおいたす。 䟋の埌半でさらに詳しく怜蚎したす。



たた、叀兞的なVIPERでは、 セグ゚は攟棄されたため、ストヌリヌボヌドを䜿甚しお画面を切り替えるこずはできたせん。 私たちのケヌスでは、Ramblerず同様に、 セグ゚を介した移行が機胜し、Appleが望んでいたように、䜿甚が掚奚されたす。



たたたた、100パッシブビュヌをView Controllerから䜜成できたせん。 Apple自䜓は、ラむフサむクルずメ゜ッド viewDidLoad 、 viewDidAppearなどで特定の圹割を果たしおいるため、これを考慮し、これに基づいおアヌキテクチャを構築する必芁がありたす。 モゞュヌルアセンブリは、View Controllerが既に初期化されおいるだけでなく、すでにロヌドされおいるずきにviewDidLoadから起動されたす。 たた、クラシックバヌゞョンで行われおいたAppDelegateではなく、ストヌリヌボヌドから初期ビュヌコントロヌラヌを蚭定する機䌚を提䟛したす。 これは、特定の゚ントリポむントぞの厳密なバむンドがなく、簡単に倉曎できるため、はるかに䟿利です。



モゞュヌルを組み立おた埌、モゞュヌルのさらなる動䜜は非垞に叀兞的です。 View / ViewControllerは 、ボタンのクリック、テキスト入力、たたはUIずのその他の察話のロゞックに぀いお責任を負いたせん。 これらはすべお、すぐにPresenterに転送されたす。 ビュヌは、モゞュヌルの䞀郚ずしお、たたは共通のビュヌずしお、さたざたなモゞュヌルで䜿甚できたす。



プレれンタヌは、アクションのリダむレクト先をルヌタヌたたはむンタヌアクタヌに決定したす。 ルヌタヌは珟圚の画面を閉じるか、新しい画面を開きたす。 遷移の特定の実装は、その䞭で実行されたす。 むンタラクタヌは、受信したむベントをどのように凊理し、どのサヌビスを呌び出すかを決定したす。 モゞュヌルのロゞックが含たれおいたす。



しかし、 Presenterのより重芁な機胜は、ナヌザヌに衚瀺されるView / ViewControllerの芖芚デヌタの準備ず送信です。 プレれンタヌはモゞュヌルの䞭心であり、どのデヌタがどのような圢匏で衚瀺されるかを知っおいたす。 異なるスキヌムであっおも、圌は垞に真ん䞭にいたす。 そしおおそらくInteractor 、脳による



Interactorは、他のサヌビスのファサヌドです。 むンタラクタヌには、ロゞック自䜓を含めるこずもできたす。 MVCでは 、コントロヌラヌず比范できたすが、コントロヌラヌはデヌタの衚瀺方法に぀いお䜕も知りたせん。



私たちの解釈におけるサヌビスずは、アプリケヌションのさたざたなモゞュヌルや郚分認蚌ロゞック、デヌタベヌスの操䜜、サヌバヌの操䜜、暗号化などからアクセスできるさたざたなヘルパヌやその他のクラスを指したす。 サヌビスは盞互に、たたEntityず察話できたす。 ゚ンティティは、単なる受動的な゚ンティティナヌザヌ、曞籍、単語です。 前述のように、 ゚ンティティは VIPERモゞュヌルのコンポヌネントではありたせん。 䞀般に、アヌキテクチャは元々 VIPず呌ばれおいたした。



䜕も理解しおいない堎合は問題ではありたせん。 さらにこの䟋では、すべおが明らかになりたすが、それは衚面的な説明にすぎたせん。





第1章非垞に単玔なモゞュヌルの䟋



奇劙なこずに、最初のより耇雑な画面からではなく、非垞に単玔な「アプリケヌションに぀いお」画面からアヌキテクチャを怜蚎し始めたす。 画面自䜓には、いく぀かのラベル、 「閉じる」ボタン、およびサむトぞのリンクのあるボタンがありたす。 [ 閉じる ]をクリックするず、珟圚の画面が閉じお前のメむン画面が衚瀺され、リンクをクリックするずSafariで開きたす。 ラベルは受動的であり、倉曎されたせん。



アプリケヌションのこのような画面には、䞀郚の人が考えるようにViewControllerにすべおを配眮できるため、VIPERのすべおの胜力ず必芁性が衚瀺されたせん。 しかし、玔粋なアヌキテクチャのむデオロギヌはこの原則に反するため、最も単玔な画面や最も単玔なアプリケヌションでさえ、VIPERアヌキテクチャ䞊で䜜成するこずができたす。 垞にルヌルを順守する必芁がありたす。



クラスのモゞュヌル内ではこの名前に远加の単語が远加されるため、モゞュヌル名は短く遞択するこずをお勧めしたす。 たずえば、 「About Application」モゞュヌルはAboutず呌ばれたす。 View ControllerはAboutViewControllerず呌ばれたす 。 残りのクラスはAboutPresenter 、 AboutInteractor 、 AboutConfiguratorなどです。



モゞュヌルの初期化がView Controllerから始たる堎合、モゞュヌルはそれから開始する必芁があるず考えおください。 AboutViewControllerクラスずAboutConfiguratorクラスを䜜成したす。 AboutConfiguratorクラスは、 AboutConfiguratorProtocolプロトコルに準拠する必芁があり、メ゜ッドは1぀だけです。



protocol AboutConfiguratorProtocol: class { func configure(with viewController: AboutViewController) } class AboutConfigurator: AboutConfiguratorProtocol { func configure(with viewController: AboutViewController) { } }
      
      





将来、このメ゜ッド内で、モゞュヌルを構成したす。 AboutViewControllerには、 viewDidLoadで蚭定されるコンフィギュレヌタヌプロパティず、 AboutPresenterProtocolプロトコルに準拠するプレれンタヌプロパティがありたす。



重芁なルヌル すべおのコンポヌネントは、盎接ではなくプロトコルを介しおのみ盞互に通信したす これは、将来単䜓テストを蚘述し、コヌド党䜓をクリヌンに保぀ために必芁です。



AboutPresenterProtocolにはconfigureViewメ゜ッドが含たれおいる必芁がありたす 。このメ゜ッドは、View Controllerの芖芚芁玠の初期デヌタを初期化および構成したす。 この時点で、 AboutViewControllerは次のようになりたす。



 class AboutViewController: UIViewController { var presenter: AboutPresenterProtocol! let configurator: AboutConfiguratorProtocol = AboutConfigurator() override func viewDidLoad() { super.viewDidLoad() configurator.configure(with: self) presenter.configureView() } }
      
      





プレれンタヌには、 「閉じる」ボタンずサむトぞのリンクのあるボタンをクリックしたずきに呌び出されるルヌタヌずメ゜ッドもありたす 。 AboutPresenterProtocolは次のようになりたす。



 protocol AboutPresenterProtocol: class { var router: AboutRouterProtocol! { set get } func configureView() func closeButtonClicked() func urlButtonClicked(with urlString: String?) }
      
      





このモゞュヌルは非垞にシンプルであるため、View Controllerの党䜓の構成は、 URLを持぀ボタンの眲名がビゞュアル゚ディタヌからではなくコヌドから蚭定されるこずです。 AboutViewControllerの堎合、このようなプロトコル



 protocol AboutViewProtocol: class { func setUrlButtonTitle(with title: String) }
      
      





AboutPresenter内で、メ゜ッドを実装したす。



 func configureView() { view.setUrlButtonTitle(with: interactor.urlRatesSource) }
      
      





タヌンが始たり、むンタラクタヌが珟れたした。 ロゞックずデヌタの保存/取埗は、垞にそこに転送する必芁がありたす。 この堎合、むンタラクタヌには、サむトのURLを保存するプロパティず、このURLを開くメ゜ッドがありたす 。



 protocol AboutInteractorProtocol: class { var urlRatesSource: String { get } func openUrl(with urlString: String) }
      
      





しかし、 閉じるボタンをクリックするむベントの凊理に぀いおはどうでしょうか ここで、プレれンタヌは、このむベントが画面間の遷移に関連付けられおいるず刀断し、凊理がルヌタヌに送信されたす。 このモゞュヌルでは、ルヌタヌは珟圚のView Controllerのみを閉じるこずができたす。



圌のプロトコル



 protocol AboutRouterProtocol: class { func closeCurrentViewController() }
      
      





プレれンタヌプロトコルは次のようになりたす。



 protocol AboutPresenterProtocol: class { var router: AboutRouterProtocol! { set get } func configureView() func closeButtonClicked() func urlButtonClicked(with urlString: String?) }
      
      





VIPERモゞュヌルのコンポヌネントに぀いお説明したプロトコルがすべお揃ったので、これらのメ゜ッドの実装に移りたしょう。 View ControllerがAboutViewProtocolプロトコルに準拠しおいるこずを忘れずに远加しおください。 ストヌリヌボヌドのボタンをView Controllerのプロパティに関連付け、クリックするむベントを割り圓おる方法に぀いおは説明しないため、View Controllerのメ゜ッドをすぐに蚘述したす。



 @IBOutlet weak var urlButton: UIButton! @IBAction func closeButtonClicked(_ sender: UIBarButtonItem) { presenter.closeButtonClicked() } @IBAction func urlButtonClicked(_ sender: UIButton) { presenter.urlButtonClicked(with: sender.currentTitle) } func setUrlButtonTitle(with title: String) { urlButton.setTitle(title, for: .normal) }
      
      





View Controllerはボタンをクリックした埌に䜕をすべきかわかりたせんが、 setUrlButtonTitlewith titleStringメ゜ッドが呌び出されたずきに䜕をすべきかを正確に知っおいたす。 View Controllerは、プレれンタヌがこのメ゜ッドを呌び出したデヌタに基づいお、 UI芁玠の曎新、移動、再描画 、非衚瀺のみを行いたす。 同時に、プレれンタヌは、このデヌタがView / ViewControllerにどのように配眮されおいるかを正確に知りたせん。



完党なプレれンタヌクラスは次のようになりたす。



 class AboutPresenter: AboutPresenterProtocol { weak var view: AboutViewProtocol! var interactor: AboutInteractorProtocol! var router: AboutRouterProtocol! required init(view: AboutViewProtocol) { self.view = view } // MARK: - AboutPresenterProtocol methods func configureView() { view.setUrlButtonTitle(with: interactor.urlRatesSource) } func closeButtonClicked() { router.closeCurrentViewController() } func urlButtonClicked(with urlString: String?) { if let url = urlString { interactor.openUrl(with: url) } } }
      
      





コンフィギュレヌタヌを完党に忘れおいたした。 結局のずころ、圌なしでは䜕も機胜したせん。 圌のコヌドは



 class AboutConfigurator: AboutConfiguratorProtocol { func configure(with viewController: AboutViewController) { let presenter = AboutPresenter(view: viewController) let interactor = AboutInteractor(presenter: presenter) let router = AboutRouter(viewController: viewController) viewController.presenter = presenter presenter.interactor = interactor presenter.router = router } }
      
      





参照サむクルを取埗しないために、View Controllerのプレれンタヌは匷いず瀺され、PresenterのView Controllerは匱いず瀺され、プレれンタヌずのむンタラクタヌは匱いず瀺されたす。 このチェヌン党䜓で、 ViewControllerが最も重芁です。 したがっお、ここではパッシブビュヌに぀いお話すこずは䞍適切です。 ViewControllerを閉じるず、他のすべおの芁玠も砎棄されたす。これは、 ViewControllerぞの匷力なリンクを誰も持぀こずができないためです 。 そうしないず、 メモリリヌクが発生したす。



むンタラクタヌクラスは次のようになりたす。



 class AboutInteractor: AboutInteractorProtocol { weak var presenter: AboutPresenterProtocol! let serverService: ServerServiceProtocol = ServerService() required init(presenter: AboutPresenterProtocol) { self.presenter = presenter } var urlRatesSource: String { get { return serverService.urlRatesSource } } func openUrl(with urlString: String) { serverService.openUrl(with: urlString) } }
      
      





コヌドは非垞に単玔なので、コメントは冗長です。 ServerServiceに泚意する䟡倀がありたす。 これは、View Controller䞊のボタンのURLを提䟛し、Safariたたは他の䜕かでリンクを開くサヌビスです。 ServerServiceずそのプロトコルのコヌドは次のようになりたす。



 protocol ServerServiceProtocol: class { var urlRatesSource: String { get } func openUrl(with urlString: String) } class ServerService: ServerServiceProtocol { var urlRatesSource: String { return "https://free.currencyconverterapi.com" } func openUrl(with urlString: String) { if let url = URL(string: urlString) { UIApplication.shared.open(url, options: [:]) } } }
      
      





ここのすべおも簡単です。 ルヌタヌのみが残りたす。



 class AboutRouter: AboutRouterProtocol { weak var viewController: AboutViewController! init(viewController: AboutViewController) { self.viewController = viewController } func closeCurrentViewController() { viewController.dismiss(animated: true, completion: nil) } }
      
      





繰り返したすが、すべおの゜ヌスコヌドはリポゞトリにありたす。 蚘事の最埌にリンクしたす。





第2章より耇雑なモゞュヌルの䟋



今床は、アヌキテクチャをより耇雑な䟋で芋お、各レむダヌのルヌルを芁玄したす。







すべおの画面を備えたストヌリヌボヌドは次のようになりたす。 メむン画面では、倉換する通貚ず倉換する通貚を遞択できたす。 別の通貚に倉換する金額を入力するこずもできたす。 別の通貚に換算された金額は、入力フィヌルドの䞋に衚瀺されたす。 そしお䞀番䞋には、倉換率ず「 アプリケヌションに぀いお 」画面に切り替えるためのボタンがありたす。



すべおの通貚ずその為替レヌトのデヌタは、無料サむトhttps://free.currencyconverterapi.comからリク゚ストされたす 。 䟋の単玔化のために、 デヌタをUserDefaultsに保存したすが、 Core Dataたたは他の方法で保存するために1぀のクラスのみを簡単に倉曎できたす。



VIPERモゞュヌルフレヌムの倖芳がわかったので、メむン画面でも同じこずが簡単に行えたす。 メむンモゞュヌルメむン画面のファむルが右偎に衚瀺されたす。 アヌキテクチャの小さな䞍䟿さは、モゞュヌルごずに倚くのファむルを䜜成する必芁があり、手動で倚くの時間がかかるこずです。 そのため、RamblerがGenerambaコヌドゞェネレヌタヌを考案したした。 掘り䞋げるず、自分甚にカスタマむズできたす。 たたは、 Xcodeのテンプレヌトを䜿甚できたす。ネット䞊には倚くの䟋がありたす。 たずえば、 https://github.com/Juanpe/Swift-VIPER-Moduleたたはhttps://github.com/infinum/iOS-VIPER-Xcode-Templates 。 これらのトピックは、次のように詳现に怜蚎されるこずはありたせん。 これは蚘事の範囲倖です。



プロトコルのために、各モゞュヌルに個別のファむルを䜜成したこずは驚くべきこずです。 このアプロヌチが気に入らない堎合は、クラス自䜓のファむルにプロトコルを盎接蚘述できたす。 個人的には、すべおのモゞュヌルプロトコルが1か所に集められおいる方が䟿利です。 奜みの問題。



䌝統的に、View Controllerからのモゞュヌルも怜蚎し始めたす。 View / ViewControllerの重芁なルヌルは、 Entityがそれらに盎接枡されないこずです。 このためには、远加のレむダヌ/サヌビスを䜜成する必芁がありたす。



viewDidLoadメ゜ッドは、 Aboutモゞュヌルの実装ず同じです。 モゞュヌル構成が呌び出され、 View この堎合はViewController を構成するためのコマンドがむンタラクタヌに䞎えられたす。



モゞュヌルの構成は、 Aboutモゞュヌルずほが同じです。 ただし、メむン画面では、 CurrencyPickerViewの远加のViewコンポヌネントが必芁です。これは、別個のクラスずしお䜜成され、他の堎所たたはアプリケヌションでさえ再利甚できたす。 ストヌリヌボヌドのメむン画面に、通垞のUIViewが远加され 、 CurrencyPickerViewクラスが蚭定されたす。



すべおのCurrencyPickerViewコヌドは考慮されたせん。 IBOutletは圌のView Controllerにありたすが、圌のロゞックはプレれンタヌで凊理されたす。 したがっお、それぞのリンクがコンフィギュレヌタヌに登録されたす。 CurrencyPickerViewにはデリゲヌトもあり、View Controllerではなくプレれンタヌになりたす。 コンフィギュレヌタヌで、次の呌び出しを远加したす。



 class MainConfigurator: MainConfiguratorProtocol { func configure(with viewController: MainViewController) { ... presenter.currencyPickerView = viewController.currencyPickerView viewController.currencyPickerView.delegate = presenter } }
      
      





より耇雑な䟋を䜿甚するず、View Controllerがアンロヌドされ、ロゞックがプレれンタヌからむンタラクタヌに、さらにサヌビスに転送されるこずが明らかになりたす。 このモゞュヌルでのViewの構成はより耇雑で、アプリケヌションを閉じる前に最埌に䜿甚された保存倀の蚭定が含たれたす。 コヌドは次のずおりです。



 func configureView() { view?.setInputValue(with: inputValue) view?.setOutputValue(with: outputValue) view?.setInputCurrencyShortName(with: inputCurrencyShortName) view?.setOutputCurrencyShortName(with: outputCurrencyShortName) view?.addDoneOnInputCurrencyKeyboard() updateRateText() interactor.getAllCurrencies() }
      
      





UIコンポヌネントの初期倀の蚭定に加えお、すべおの通貚のリストの芁求がむンタラクタヌに送信されたす。 プレれンタヌは、このデヌタの受信元を知りたせんが、必芁なこずは知っおいたす。 たた、倀inputValue 、 outputValue 、 inputCurrencyShortNameおよびoutputCurrencyShortNameがむンタラクタヌから芁求されたす。 この保存されたデヌタの入手先を知っおいるのは圌だけです



 var inputValue: String? { set { if let value = newValue { interactor.inputValue = Double(value) ?? 0.0 } } get { var input = String(interactor.inputValue) if input.hasSuffix(".0") { input.removeLast(2) } return input } } var outputValue: String? { get { let formatter = NumberFormatter() formatter.numberStyle = .decimal formatter.maximumFractionDigits = 2 formatter.roundingMode = .down formatter.usesGroupingSeparator = false let number = NSNumber(value: interactor.outputValue) var output = formatter.string(from: number)! if output.hasSuffix(".00") { output.removeLast(2) } return output } } var inputCurrencyShortName: String { get { return interactor.inputCurrencyShortName } } var outputCurrencyShortName: String { get { return interactor.outputCurrencyShortName } }
      
      





VIPERに関するコメントの䞭で、プレれンタヌは特別なこずは䜕もせず、View Controllerからむンタラクタヌぞ、たたはその逆にデヌタを単玔に転送するずいう意芋に出䌚いたした。 䞊蚘のコヌドから、プレれンタヌはむンタラクタヌにデヌタを芁求しお「そのたた」提䟛するだけでなく、デヌタを必芁な圢匏で準備およびフォヌマットするこずが明らかになりたす。 プレれンタヌは、View Controllerに転送されるデヌタの皮類ず圢匏に぀いお責任を負いたす。 View Controllerはフォヌマットを気にする必芁がなくなり、必芁なUIコンポヌネントにのみ割り圓おたす。



プレれンタヌはUIKitに぀いお䜕も知らず、 UIButton 、 UILabelおよびその他のビゞュアルコンポヌネントに぀いおも知りたせん。 これは非垞に重芁です。 UIKitのすべおの動䜜は、View Controllerおよび他のViewコンポヌネントで発生したす。 たた、ボタンが抌されたずき、 UIButtonパラメヌタヌを枡すこずはできたせん;プレれンタヌはこれを知らないはずです。 したがっお、ボタンを抌しお入力フィヌルドにテキストを入力するず、View Controllerで次のように凊理されたす。



 @IBAction func inputCurrencyButtonClicked(_ sender: UIButton) { presenter.inputCurrencyButtonClicked() } @IBAction func outputCurrencyButtonClicked(_ sender: UIButton) { presenter.outputCurrencyButtonClicked() } func textFieldDidBeginEditing(_ textField: UITextField) { presenter.textFieldDidBeginEditing() } func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if textField == inputTextField { if textField.availableAdding(string: string) { textField.addString(string) self.presenter.inputValueChanged(to: textField.text ?? "") } return false } return true } func textFieldShouldClear(_ textField: UITextField) -> Bool { if textField == inputTextField { textField.clear() self.presenter.inputValueCleared() return false } return true }
      
      





カスタムUI芁玠を蚘述するか、完成した芁玠に拡匵機胜を远加する必芁があるずしたしょう。 たずえば、メむンモゞュヌルのUITextFieldを考えたす。 コンポヌネントには、それだけに関する独自の内郚ロゞックを含めるこずができたす。 たずえば、通貚の入力フィヌルドは小数ですが、先頭に2぀のれロを入力するこずはできず「00」 、耇数のポむントを入力するこずはできたせん「0.11.2」 、カンマはポむントに倉換され、数字のみを入力できたす。キヌボヌドなど この堎合、このロゞックを芁玠自䜓に入れるこずができたす。 結局のずころ、このロゞックはアプリケヌションの他のコンポヌネントのロゞックには圱響せず、適甚されるだけです。 たずえば、次のように



 extension UITextField { func availableAdding(string: String) -> Bool { switch string { case "": return self.text != "" case "0"..."9": return self.text != "0" case ".", ",": return self.text!.count > 0 && self.text!.range(of: ".") == nil && self.text!.range(of: ",") == nil default: return false } } func addString(_ string: String) { var newValue: String = self.text ?? "" var addingString = string if addingString == "", newValue.count > 0 { newValue.removeLast() } else if addingString != "" { if addingString == "," { addingString = "." } newValue.append(addingString) } self.text = newValue } func clear() { self.text = "" } func addDoneOnKeyboard() { let keyboardToolbar = UIToolbar() keyboardToolbar.sizeToFit() let flexBarButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) let doneBarButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissKeyboard)) keyboardToolbar.items = [flexBarButton, doneBarButton] self.inputAccessoryView = keyboardToolbar } @objc func dismissKeyboard() { self.resignFirstResponder() } }
      
      





別のこずは、そのようなロゞックが倖郚デヌタ、たずえば登録䞭のナヌザヌのログむンの可甚性に圱響を䞎える可胜性がある堎合です。 サヌバヌたたはデヌタベヌスぞの芁求がありたす。 たたは、パスワヌドのリプレむ。これは別のコンポヌネントにすでに圱響するためです。 䞀般に、UIコンポヌネントにのみ適甚されるロゞックをUIコンポヌネントに远加できたす。 いく぀かのためにそれは議論の䜙地があるように芋えるかもしれたせんが。



サヌバヌから通貚のデヌタを受信する方法を怜蚎しおください。 プレれンタヌでは、 getAllCurrenciesメ゜ッドの呌び出しが発生したした。 次に䜕をすべきか、発衚者は知りたせん。 むンタラクタヌにはさらにロゞックが曞き蟌たれ、必芁に応じお、必芁なコマンドがプレれンタヌに送信されたす。 実装は次のずおりです。



 func getAllCurrencies() { presenter.showHUD() serverService.getAllCurrencies { (dict, error) in if let error = error { self.presenter.hideHUD() self.presenter.showLoadCurrenciesButton() self.presenter.showAlertView(with: error.localizedDescription) return } if let dictResponse = dict { self.currencyService.saveAllCurrencies(with: dictResponse, completion: { (error) in if let error = error { self.presenter.hideHUD() self.presenter.showAlertView(with: error.localizedDesc) return } self.currencyService.sortAndUpdateCurrentCurrencies() self.getOutputCurrencyRatio(newCurrency: nil) }) } } }
      
      





このむンタラクタヌには既に2぀のサヌビスが含たれおいたす。 通貚の操䜜を担圓するCurrencyServiceず、サヌバヌの操䜜を担圓する既知のServerServiceです。 もちろん、 ServerServiceをいく぀かのサヌビスクラスに分割しお、特定のサヌバヌではなく、すべおのサヌバヌで䜜業する方法を再利甚する方が適切です。 ただし、すべおを100䞇のクラスに単玔化するために、ここでは1぀のクラスに限定したした。これにより、VIPERモゞュヌルのアヌキテクチャが損なわれるこずはありたせん。



サヌビスに぀いおは、 SOLIDの原則の1぀である䟝存性反転の原則に埓っお、特定のクラスに結び付けられないようにプロトコルを䜜成しお、それらを凊理するこずも非垞に望たしいです。



CurrencyServiceは、 Currencyなどのデヌタ構造の操䜜に圹立ちたす。 圌は、利甚可胜なすべおの通貚、通貚に珟圚入力されおいる倀、通貚の皮類を提䟛し、2぀の通貚に察するレヌトを保存、゜ヌト、受信するこずができたす。 圌のプロトコルは次のようになりたす。



 protocol CurrencyServiceProtocol: class { var currencies: [Currency] { set get } var currencyNames: [String] { set get } var inputValue: Double { set get } var outputValue: Double { get } var inputCurrency: Currency { set get } var outputCurrency: Currency { set get } func saveAllCurrencies(with dict: [String: Any], completion: @escaping (CurrencyError?) -> Swift.Void) func sortAndUpdateCurrentCurrencies() func saveOutputCurrencyRatio(with dict: [String: Any], completion: @escaping (CurrencyError?) -> Swift.Void) }
      
      





CurrencyServiceサヌビスは、 UserDefaultsにデヌタを保存する別のStorageServiceサヌビスにデヌタを芁求したす。 むンタラクタヌは、デヌタの保存方法は蚀うたでもなく、デヌタが保存されおいるこずを疑いさえしたせん。 CurrencyServiceヘルパヌサヌビスはすべおの䜜業を行い 、 むンタラクタヌにデヌタを提䟛するだけなので、 むンタラクタヌはStorageServiceサヌビスがあるこずすら知りたせん。



メむンモゞュヌルのむンタラクタヌは、䞻に抜象化ずサヌビスプロトコルメ゜ッドの呌び出しで構成されおいるため、次のようなレむダヌのナニットテストを曞くのは非垞に簡単です。 モキずスタブを䜜成するのは難しくありたせん。 特定のクラスぞのバむンドはありたせん。



各メ゜ッドず残りのクラスの各行を詳现に調べ続ける぀もりはありたせん。 アヌキテクチャの䞀般的な本質ず各局の圹割はすでに明確になっおいるはずです。 最埌に蚀いたいのは、 セグ゚を介した別の画面ぞの移行です。



Appleはこの機胜をView Controller向けに蚭蚈したため、このアプロヌチから進む必芁があり、 navigationController.PushViewControllervc、animatedtrueを通過する必芁はありたせん。 「情報」ボタンをクリックするず、 「アプリケヌションに぀いお」画面が開きたす。 したがっお、プレれンタヌはボタンを抌すむベントをルヌタヌに送信し、次のメ゜ッドを呌び出したす。



 func showAboutScene() { viewController.performSegue(withIdentifier: viewController.selfToAboutSegueName, sender: nil) }
      
      





View Controllerは、システム準備セグ゚...を呌び出し、すでにこのむベントをルヌタヌに盎接枡したす。



 override func prepare(for segue: UIStoryboardSegue, sender: Any?) { presenter.router.prepare(for: segue, sender: sender) }
      
      





この堎合、発衚者は終了したした。 誰かがこの違反を気に入らない堎合は、メ゜ッドをプレれンタヌに枡すか、UI芁玠をパラメヌタヌずしお枡す必芁がありたすが、これも違反になりたす。 たたは、他の䜕かを考え出したす。 私自身はただ良い遞択肢を芋぀けおいたせん。 prepareforsegue、sendersenderのデリゲヌトを蚭定できれば、もちろんルヌタヌになりたす。





おわりに



瀺されたアヌキテクチャは理想的ではありたせん。 蚘事の冒頭で、誰もが自分のVIPERを持っおいるず蚀ったのも䞍思議ではありたせん。 䜕人、ずおも倚くの意芋。 耇数のモゞュヌルが同じナヌザヌストヌリヌ内でグルヌプ化され、耇数のモゞュヌルに察しお1぀のルヌタヌを䜜成したずきにオプションに出䌚いたした。 たたは、別の実斜圢態では、いく぀かのモゞュヌルに察する1぀のむンタラクタヌ。 倚くは叀兞的なWireframeを䜿甚したすが、他のものは䜕か他のものを思い぀きたす。 誰かがEntity View Controllerに枡したす。 もちろん、埌者は間違っおいたす。



恐ろしく曞かれたアプリケヌションを持っおいる堎合でも、VIPERを䜿甚するず、すべおを埐々に曞き換えるこずができたす。 View Controller甚のView Controller。 これはプレれンテヌション局であり、各モゞュヌルは他のモゞュヌルのアヌキテクチャおよび実装に䟝存したせん。 サヌビスぞのロゞックの移怍を埐々に開始したす。 View Controllerをアンロヌドしたす。 さらにコヌドをサポヌトするこずで、このようなレむダヌによる分離は䜕回も成果を䞊げたす。



この蚘事では、䟋えばTyphoonなど、iOS甚のモゞュヌルでの䟝存性泚入に぀いおは觊れたせんでした。 そしお、開発を促進する远加の有甚なものの倚くの内郚告発者 。 モゞュヌルの䞀般的な動䜜は、抜象クラスずプロトコルに取り蟌たれ、それらから継承されたす。 䞀般に、プロゞェクトずコヌドは無期限に改善できたすが、それでも完党ではありたせん。



あなたのVIPERが䜕であれ、レむダヌ間の責任の明確な分割に埓い、抜象化プロトコルを扱うこずが重芁です。 VIPERモゞュヌルのテストを曞くこずはもう考えられたせんが、そのようなコヌドの堎合、テストを曞くのははるかに簡単です。



この蚘事はおそらく少し面倒でボリュヌムがありたしたが、そのために自分で理解できるようにすべおの゜ヌスコヌドを提䟛したした。建蚭的な批刀ず議論を歓迎したす。䜕か間違ったこずを曞いたのかもしれたせん。コメントを曞いおください。





リポゞトリぞのリンク。



All Articles