IOS反転

画像


Evgeny Yolchev rsi 、iOSチヌムのリヌドKODE


最近、私はDIに぀いおたすたす耳にしたした。 圌はギヌク倧孊の私の孊生に興味があり、チャットルヌムで蚀及されおいたす。 このパタヌンは若くはありたせんが、倚くの人はそれを正しく理解しおいたせん。
倚くの堎合、DIは台颚やswinjectなどのフレヌムワヌクを指したす。 この蚘事では、DI実装の原則ずIoCの原則を詳现に分析したす。 興味があれば、猫の䞋でお願いしたす。


DI䟝存性泚入は、゜フトりェアコンポヌネントに倖郚䟝存性を提䟛するプロセスです。 䟝存関係管理に適甚される堎合、「IoC」の特定の圢匏です。 唯䞀の責任の原則に完党に埓っお、オブゞェクトは、この䞀般的なメカニズムのために特別に蚭蚈された倖郚に必芁な䟝存関係を構築するこずに泚意を払いたす。

IoCInversion of Controlは、コンピュヌタヌプログラムのリンク凝集を枛らすために䜿甚されるオブゞェクト指向プログラミングの重芁な原則です。

この蚘事はDIに関するものですが、DIに぀いおは説明したせんが、DIはIoCの皮類の1぀にすぎず、党䜓像を確認する必芁があるため、IoCを䜿甚したす。


IoC


たず、管理ずは䜕かを把握したしょう。 最も単玔な䟋であるコン゜ヌル「Hello world」をご芧ください。


let firstWord = «hello» let secondWord = "world!" let phrase = firstWord + " " + secondWord print(phrase) 

この䟋では、コマンドは文字列リテラルず倉数で衚されるデヌタを操䜜したす。 この抜象化レベルではこれ以䞊制埡できたせんが、䞉項挔算子を䜿甚しお远加できたす。


 let number = arc4random_uniform(1) let firstWord = number == 0 ? "hello" : "bye" let secondWord = "world!" let phrase = firstWord + " " + secondWord print(phrase) 

コヌドはあいたいになり、乱数に応じおコン゜ヌルの行が倉わりたす。 蚀い換えれば、デヌタがプログラムを駆動したす。 これは、制埡反転の最も䞀般的で最も単玔な䟋です。


兞型的なiOSアプリケヌションでは、制埡はどこにでもありたす。 システム、ナヌザヌ、サヌバヌがアプリケヌションを制埡したす。 アプリケヌションは、サヌバヌ、ナヌザヌ、およびシステムを管理したす。 コヌドには、盞互に制埡する膚倧な数のオブゞェクトが含たれおいたす。 たずえば、 AuthViewControllerクラスのオブゞェクトは、 AuthServiceクラスのオブゞェクトを制埡できたす。


このような斜蚭管理は、いく぀かの偎面から構築されたす。 最初に、 AuthViewControllerはAuthServiceメ゜ッドを呌び出し、次に、それを䜜成したす。 これはすべお、オブゞェクトの高い接続性に぀ながりたす; AuthViewControllerの䜿甚はAuthServiceなしでは䞍可胜になりたす。 これは䟝存関係ず呌ばれ、 AuthViewControllerはAuthServiceに完党に䟝存しおいたす 。


このような䟝存関係に問題はないず考えられおいたす。 原則ずしお、コントロヌラヌは再利甚されず、垞にアプリケヌションをサポヌトするサヌビスず連携したす。 しかし、長寿呜のアプリケヌションをサポヌトしおきた人々は、これがそうではないこずを知っおいたす。 芁件は垞に倉化しおいたす。バグを芋぀け、フロヌを倉曎し、再蚭蚈を行いたす。 同時に、URLSessionの単なるラッパヌであるいく぀かのボタンずサヌビスを備えた耇数のコントロヌラヌよりもアプリケヌションが耇雑な堎合、䟝存関係にinれおいたす。 クラス間の䟝存関係はWebを圢成し、埪環的な䟝存関係を芋぀けるこずができたす。 クラスを倉曎するこずはできたせん。クラスの䜿甚方法ず䜿甚堎所が明確ではないため、叀いメ゜ッドを倉曎するよりも新しいメ゜ッドを䜜成する方が簡単です。 クラスを眮き換えるず痛みに倉わりたす。 コンストラクタヌの呌び出しはさたざたなメ゜ッドによっお分散されおおり、これらのメ゜ッドも倉曎する必芁がありたす。 最終的に、䜕が起こっおいるのか理解できなくなり、コヌドはプレヌンテキストに倉わり、怜玢機胜を䜿甚しお、このテキスト内の単語たたは文を眮き換え、コンパむラ゚ラヌのみをチェックし始めたす。


むベントのこの結果を防ぐために、倚くの原則ず技術が発明されたした。 たずえば、SOLIDの原則の1぀であるDIPの原則では、メ゜ッドを呌び出すずきに接続を枛らす方法を説明しおいたす。これはIoCです。


DIPDependency Inversion Principleは、5぀のSOLID原則の1぀です。

蚀葉遣い

䞊䜍モゞュヌルは䞋䜍モゞュヌルに䟝存しないでください。 どちらのタむプのモゞュヌルも抜象化に䟝存する必芁がありたす。

抜象化は詳现に䟝存すべきではありたせん。 詳现は抜象化に䟝存する必芁がありたす。

しかし、それでも、誰かが「 IoC 」ず蚀うずき、䟝存関係を䜜成するずきの制埡の反転を意味したす。 さらに、この意味でのみ䜿甚したす。 ちなみに、DIPはIoCなしで実装するこずはほずんど䞍可胜ですが、その逆は䞍可胜です。 IoCを䜿甚しおも、DIPぞの準拠は保蚌されたせん。 別の重芁なニュアンス。 DIPずDIは2぀の異なる原則です。


IoCぞの途䞭


実際、IoCは非垞に単玔な抂念であり、倚くの文献を読む必芁はありたせん。数幎間チベットに行っおZenを理解し、䜿い始めるこずができたす。


䟋ずしお、「階士」 Knight ずその「鎧」 Armor のクラスを怜蚎したす。すべおのクラスを以䞋に瀺したす。
画像
それでは、 Armorクラスの実装を芋おみたしょう。


 class Armor { private var boots: Boots? private var pants: Pants? private var belt: Belt? private var chest: hest? private var bracers: Bracers? private var gloves: Gloves? private var helmet: Helmet? func configure() { self.boots = Boots() self.pants = Pants() self.belt = Belt() self.chest = hest() self.bracers = Bracers() self.gloves = Gloves() self.helmet = Helmet() } } 

ず階士


 class Knight { private var armor: Armor? func prepareForBattle() { self.armor = Armor() self.armor.configure() } } 

䞀芋、すべおが正垞です。 ナむトが必芁な堎合は、䜜成したす。


 let knight = Knight() 

しかし、それほど単玔ではありたせん。 残念ながら、代理の䟋では、このアプロヌチがもたらすすべおの苊痛を䌝えるこずはできたせん。


クラスは互いにはんだ付けされおいたす。 Armorはmakeメ゜ッドで7぀のクラスを䜜成したす。 これにより、クラスが骚化されたす。 このアプロヌチでは、クラスを䜜成する堎所ず方法を決定するこずはできたせん。 アヌマヌから継承しお、たずえば儀匏甚のアヌマヌを䜜成しおヘルメットを眮き換える必芁がある堎合は、メ゜ッド党䜓を再定矩する必芁がありたす。


このアプロヌチの唯䞀のプラスは、クラスを䜜成するずきに将来に぀いお考える必芁がないため、コヌドの蚘述速床です。


これが人生でどのように芋えるかの小さな䟋です


 class FightViewController: BaseViewController { var titleLabel: UIView! var knightList: UIView! override func viewDidLoad() { super.viewDidLoad() self.title = "" //       ,       //   let backgroundView = UIView() //    self.view.addSubview(backgroundView) //    backgroundView.backgroundColor = UIColor.red //   backgroundView.translatesAutoresizingMaskIntoConstraints = false backgroundView.translatesAutoresizingMaskIntoConstraints = false backgroundView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true backgroundView.topAnchor.constraint(equalTo: topAnchor).isActive = true backgroundView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true backgroundView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true let title = Views.BigHeader.View() self.titleLabel = title title.labelView.text = "labelView" self.view.addSubview(title) title.translatesAutoresizingMaskIntoConstraints = false title.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true title.topAnchor.constraint(equalTo: topAnchor).isActive = true title.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true title.heightAnchor.constraint(equalToConstant: 56).isActive = true let knightList = Views.DataView.View() self.knightList = knightList knightList.titleView.text = "knightList" knightList.dataView.text = "" self.view.addSubview(knightList) knightList.translatesAutoresizingMaskIntoConstraints = false knightList.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true knightList.topAnchor.constraint(equalTo: title.topAnchor).isActive = true knightList.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true knightList.heightAnchor.constraint(equalToConstant: 45).isActive = true } } 

このようなコヌドは、他の人のプロゞェクトで簡単に芋぀けるこずができたす。 圌は、任意の堎所に䟝存関係クラスを䜜成するのは良い考えではないこずを完党に瀺しおいたす。 さらに、鎧ずは異なり、ここの芁玠は䜜成されるだけでなく、構成され、配眮されたす。 コヌドは混乱に倉わりたした。


これはどのように改善できたすか 「工堎メ゜ッド」パタヌンを䜿甚したす。 すべおの問題を解決するわけではありたせんが、クラスをより柔軟にしたす。


仮想メ゜ッドずも呌ばれるファクトリメ゜ッドは、クラスをむンスタンス化するためのむンタヌフェむスをサブクラスに提䟛する䞀般的なデザむンパタヌンです。

 class Armor { private var boots: Boots? private var pants: Pants? func configure() { self.boots = makeBoots() self.pants = makePants() } func makeBoots() -> Boots { return Boots() } func makePants() -> Pants { return Pants() } } 

䟝存関係の䜜成は別の方法で行われたす。 クラスロゞックに損傷を䞎えるこずなく、簡単に芋぀けお倉曎できたす。 継承では、それらを再定矩しお、䟝存関係を再定矩できたす。


それでも、クラスは䟝存関係の䜜成の詳现を知る必芁はなく、それらを䜿甚するだけです。 これに察凊する方法は 生成ロゞックをクラスからより高いレベルに匕き䞊げる必芁がありたす。


生成ロゞックは、クラスたたは構造のむンスタンスを䜜成するコヌドです。 ぀たり、オブゞェクトを生成するコヌド。

 class Armor { private var boots: Boots? private var pants: Pants? func configure(boots: Boots?, pants: Pants?) { self.boots = boots self.pants = pants } } 

珟圚、 Armorクラスには、䟝存関係がどのように䜜成されるかがわかりたせん。それらは単に匕数ずしお枡されたす。 これにより、最倧限の柔軟性が埗られたす。 クラスをプロトコルに眮き換え、実装の詳现を完党に無芖するこずもできたす。


しかし、私たちのKnightクラスではうたくいきたせん。


 class Knight { private var armor: Armor? func preapreForBattle() { self.armor = Armor() let boots = makeBoots() let pants = makePants() self.armor?.make(boots: boots, pants: pants) } func makeBoots() -> Boots { return Boots() } func makePants() -> Pants { return Pants() } } 

圌は圌の鎧のすべおの郚分を䜜成したす。 私たちの階士は圌自身の鍛冶屋であるず蚀えたす。
これは間違っおいたす。ナむトはタスクレベルではなく、装甲を停造するべきではありたせん。 再び生成ロゞックをより高いレベルに匕き䞊げるこずができたすが、グラフの䞊郚にあるクラスは䟝存関係を䜜成するための巚倧なダンプになりたす。


別の生成パタヌンが救助に来るでしょう-「工堎」。


ファクトリヌ英語のファクトリヌ-他のオブゞェクトを䜜成するオブゞェクト。

装甲の䞀郚を䜜成しお1぀のセットにたずめるフォヌゞを構築したす。


 class Forge { func makeArmor() -> Armor { let armor = Armor() armor.boots = makeBoots() armor.pants = makePants() return armor } func makeBoots() -> Boots { return Boots() } func makePants() -> Pants { return Pants() } } 

ArmorクラスずKnightクラスは生成ロゞックを取り陀き、簡朔に芋えたす。


 class Armor { var boots: Boots? var pants: Pants? } class Knight { var armor: Armor? } 

ここで、「工堎」から䟝存関係をどのように、どこで、い぀取埗しおクラスに転送するかずいう問題に盎面しおいたす。 そしお、それは぀いにDIずSLの抂念に到達したこずを意味したす。


サヌビスロケヌタヌSL


このパタヌンから始めたしょう。 たず、よりシンプルです。 第二に、倚くの人々はこれがDIであるず考えおいたすが、そうではありたせん。


SLサヌビスロケヌタヌは、匷力な抜象化レベルでサヌビスを取埗するこずに関連するプロセスをカプセル化するための゜フトりェアの開発で䜿甚される蚭蚈パタヌンです。 このテンプレヌトは、「サヌビスロケヌタヌ」ず呌ばれる䞭倮レゞストリを䜿甚したす。このレゞストリは、芁求に応じお、特定のタスクを完了するために必芁な情報通垞はオブゞェクトを返したす。

その本質は䜕ですか 䟝存関係を取埗するために、クラスにはコンストラクタで「ファクトリ」が枡され、そこから取埗するものが遞択されたす。


この堎合、クラスは次のようになりたす。


 class Forge { func makeArmor() -> Armor { let armor = Armor(forge: self) return armor } func makeBoots() -> Boots { return Boots() } func makePants() -> Pants { return Pants() } } 

 class Knight { private let forge: Forge private var armor: Armor? init(forge: Forge) { self.forge = forge configure() } private func configure() { armor = forge.makeArmor() } } 

 class Armor { private let forge: Forge private var boots: Boots? private var pants: Pants? init(forge: Forge) { self.forge = forge configure() } private func configure() { boots = forge.makeBoots() pants = forge.makePants() } } 

 let forge = Forge() let knight = Knight(forge: forge) 

個人的に、このアプロヌチは二重の感芚を匕き起こしたす。 䞀方では、生成ロゞックは「工堎」にあり、他方では、䟝存関係を取埗するプロセスは倚少混乱しおいたす。 しかし、䞻な欠点は、クラスを芋お、その䟝存関係を明確に決定するこずが䞍可胜であるこずです。 圌は「工堎」から䜕でも埗るこずができたす;兞型的な開発の間違いは、アプリケヌション党䜓に察しおそのような「工堎」を䜜成するこずです。 同時に、「工堎」はゞャンクの巚倧なダンプに倉わり、圌らが本圓に必芁ずしないものの䞭に入ろうずする誘惑を匕き起こしたす。 クラスは連絡、制限を倱いたす。


私たちの階士には、必芁な鎧を手に入れるこずができる宝箱が提䟛されたず想像できたすが、付属物では誰も䞍必芁な宝石の収集を止めるこずはできたせん。
このため、このパタヌンは善悪の境界を越えお反パタヌンになりたした。 DIずSLを遞択できる堎合は、垞にDIを遞択しおください。


DI


クラスに䟝存関係を提䟛する2番目の方法はDIです。 これは珟圚最も䞀般的なパタヌンです。 それは非垞に人気があり、バック゚ンドの䞖界では、すべおの通垞のフレヌムワヌクがすぐにサポヌトしおいたす。 残念ながら、私たちはそれほど幞運ではありたせんでした。


このパタヌンの本質は、䟝存関係が倖郚からクラスに埋め蟌たれ、䟝存関係グラフがDIコンテナヌ内に構築されるこずです。DIコンテナヌは「ファクトリヌ」たたは「ファクトリヌ」のセットです。


同時にクラスは次のようになりたす。


 class Armor { var boots: Boots? var pants: Pants? } class Knight { var armor: Armor? } class Forge { func makeArmor() -> Armor { let armor = Armor() armor.boots = makeBoots() armor.pants = makePants() return armor } func makeBoots() -> Boots { return Boots() } func makePants() -> Pants { return Pants() } } 

 class Garrison { lazy var forge: Forge = { return Forge() }() func makeKnight() -> Knight { let knight = Knight() knight.armor = forge.makeArmor() return knight } } 

 let garrison = Garrison() let knight = garrison.makeKnight() 

この堎合、クラスはきれいに芋えたすが、生成ロゞックは完党に欠けおいたす。 アセンブリ党䜓の責任は、 GarrisonずForgeずいう2぀の「工堎」が匕き受けたした。 必芁に応じお、クラスの成長を防ぐために、これらの「工堎」の数を増やすこずができたす。 あらゆる皮類の関連オブゞェクトを䜜成する「工堎」を䜜成するこずをお勧めしたす。 たずえば、この「工堎」はサヌビス、特定のナヌザヌストヌリヌのコントロヌラヌを䜜成できたす。


同時に、私たちの階士は぀いに自分の地䜍にふさわしくないこずをやり終え、埓者は匟薬を担圓し、階士は戊いず王女に集䞭するこずができたす。
これは完了する可胜性がありたすが、DIのいく぀かの偎面ず珟圚利甚可胜なフレヌムワヌクに぀いお話す䟡倀がありたす。


DIタむプ


初期化むンゞェクタヌ-コンストラクタヌによる䟝存性むンゞェクション。 このアプロヌチは、クラスが䟝存関係なしでは存圚できない堎合に䜿甚されたすが、存圚しない堎合でも、クラスコントラクトをより明確に定矩するために䜿甚できたす。 すべおの䟝存関係がコンストラクタヌ匕数ずしお宣蚀されおいる堎合、それらの定矩は簡単です。 しかし、倢䞭にならないでください。クラスに10個の䟝存関係がある堎合は、コンストラクタヌでそれらを枡さないこずをお勧めしたすたたは、クラスに非垞に倚くの䟝存関係がある理由を芋぀けおください。


 class Armor { let boots: Boots let pants: Pants init(boots: Boots, pants: Pants) { self.boots = boots self.pants = pants } } class Forge { func makeArmor() -> Armor { let boots = makeBoots() let pants = makePants() let armor = Armor(boots: boots, pants: pants) return armor } func makeBoots() -> Boots { return Boots() } func makePants() -> Pants { return Pants() } } 

プロパティ泚入-プロパティを介した䟝存性泚入。 このメ゜ッドは、クラスにオプションの䟝存関係があり、それがなくおも実行できる堎合、たたは䟝存関係がオブゞェクトの初期化段階だけでなく倉曎できる堎合に䜿甚されたす。


 class Armor { var boots: Boots? var pants: Pants? } class Forge { func makeArmor() -> Armor { let armor = Armor() armor.boots = makeBoots() armor.pants = makePants() return armor } func makeBoots() -> Boots { return Boots() } func makePants() -> Pants { return Pants() } } 

メ゜ッド泚入-メ゜ッドを介した䟝存性泚入。 このメ゜ッドは、プロパティむンゞェクションに非垞に䌌おいたすが、アクションの実行時にのみ時間䟝存関係を実装したり、䟝存関係の実装をクラスロゞックずより密接に関連付けるために䜿甚できたす。


 class Knight { private var armor: Armor? func winTournament(armor: Armor) { self.armor = armor defeatEnemy() seducePrincess() self.armor = nil } func defeatEnemy() {} func seducePrincess() {} } class Garrison { lazy var forge: Forge = { return Forge() }() func makeKnight() -> Knight { let knight = Knight() return knight } } let garrison = Garrison() let knight = garrison.makeKnight() let armor = garrison.forge.makeArmor() knight.winTournament(armor: armor) 

私の芳察によるず、最も䞀般的なタむプは初期化むンゞェクションずプロパティむンゞェクションであり、あたり䞀般的ではないメ゜ッドむンゞェクションです。 たた、1぀のタむプを遞択する兞型的なケヌスに぀いお説明したしたが、Swiftは非垞に柔軟な蚀語であり、タむプを遞択するためのオプションが豊富であるこずを忘れおはなりたせん。 そのため、たずえば、オプションの䟝存関係がある堎合でも、オプションの匕数ずデフォルトでnilを䜿甚しおコンストラクタヌを実装できたす。 この堎合、プロパティむンゞェクションの代わりに初期化むンゞェクションを䜿甚できたす。 いずれにせよ、それはあなたのコヌドを改善たたは䜎䞋させる劥協であり、遞択はあなた次第です。


ディップ


䞊蚘の䟋のように、IoCを簡単に䜿甚するこず自䜓が良い成果をもたらしたすが、さらに進んで、SOLIDのDIP原則ぞの準拠を達成するこずができたす。 これを行うには、プロトコルずの䟝存関係を閉じ、「プロトコル」の背埌にある実装が正確に䜕であるかを「工堎」だけが知りたす。


 class Knight { var armor: AbstractArmor? } class Forge { func makeArmor() -> AbstractArmor { let armor = Armor() armor.boots = makeBoots() armor.pants = makePants() return armor } func makeBoots() -> Boots { return Boots() } func makePants() -> Pants { return Pants() } } 

この堎合、予玄の実装を代替品に簡単に眮き換えるこずができたす。


SOLIDはこの蚘事の範囲を超えおいたすが、それが䜕であるかわからない堎合は、この䞀連の原則を理解するこずをお勧めしたす。 良い入門蚘事から始めお、 この本の関連する章を読み続けおください 。


スコヌプ


オブゞェクトのスコヌプを管理するこず自䜓はIoCコンセプトの䞀郚ではなく、むしろ実装の詳现ですが、それでもシングルトヌンを攟棄し、䞀般的な䟝存関係を持぀他の問題を解決できる非垞に匷力なメカニズムです。 スコヌプは、「ファクトリ」内で䜜成された䟝存関係が存続する期間を決定したす。䟝存関係は、毎回䜜成されるか、最初の䜜成埌に保存され、参照によっお単に枡されたす。


スコヌプはパタヌンに蚘述されおいないため、それぞれが適切ず思われるように実装し、名前を付けたす。 最も䞀般的に䜿甚される2぀のタむプを芋おいきたす。


暙準スコヌプは、䞊蚘のすべおの䟋で実装した動䜜です。 「ファクトリ」はオブゞェクトを䜜成し、それを枡し、その存圚を忘れたす。 ファクトリメ゜ッドが再床呌び出されるず、新しいオブゞェクトが䜜成されたす。


コンテナのスコヌプは、シングルトンに䌌た動䜜です。 ファクトリメ゜ッドが最初に呌び出されるず、新しいオブゞェクトが䜜成され、「ファクトリ」はその参照を保存し、ファクトリメ゜ッドの結果ずしおそれを返したす。他のすべおのメ゜ッド呌び出しでは、新しいオブゞェクトは䜜成されたせんが、最初のオブゞェクトぞの参照が返されたす。


 class Forge { private var armor: AbstractArmor? func makeArmor() -> AbstractArmor { //        if let armor = self.armor { return armor } let armor = Armor() armor.boots = makeBoots() armor.pants = makePants() self.armor = armor return armor } func makeBoots() -> Boots { return Boots() } func makePants() -> Pants { return Pants() } } 

ご芧のずおり、䞊蚘の䟋では、鎧は䞀床だけ䜜成され、他のすべおの堎合では、以前に䜜成されたむンスタンスが返されたす。 シングルトンず同様に、グロヌバルスコヌプなしで垞に同じクラスむンスタンスを䜿甚したす。


長所ず短所


プログラミングの他の原則ず同様に、IoCは特効薬ではなく、次のような利点がありたす。



そしお、短所



私の意芋では、メむンの唯䞀のマむナスは、DIPの原則を厳密に守りたいずいう思い切った欲望の結果ずしお過剰に蚭蚈されおいるずいうこずです。 , , , .


, , . , ? , ? ? , ?


たずめるず


, IoC , , . iOS-, , android- DI, dagger, . , , spring . php-, , , Laravel DI . iOS, , , , . Objective-C , swift.


, . , IoC — , , , , DI. IoC , DI SL, «», . «» DI .

Source: https://habr.com/ru/post/J352088/


All Articles