Ruhiger, ruhiger Streit

Bibliothekssymbol Vor drei Jahren schrieb ich einen Artikel über die DI-Bibliothek für die Swift-Sprache. Von diesem Moment an hat sich die Bibliothek stark verändert und ist der beste seiner Art , der Swinject würdig ist, und hat ihn in vielerlei Hinsicht übertroffen. Der Artikel widmet sich den Fähigkeiten der Bibliothek, enthält aber auch theoretische Überlegungen. Wer sich also für die Themen DI, DIP, IoC interessiert oder zwischen Swinject und Swinject wählt, bitte um einen Schnitt:


Was ist DIP, IoC und womit isst es?


Theorie von DIP und IoC


Die Theorie ist eine der wichtigsten Komponenten in der Programmierung. Ja, Sie können Code ohne Schulung schreiben, aber trotzdem lesen Programmierer ständig Artikel, interessieren sich für verschiedene Praktiken usw. Das heißt, auf die eine oder andere Weise bekomme ich theoretisches Wissen, um es in die Praxis umzusetzen.

Eines der Themen, die gerne nach Interviews gefragt werden, ist SOLID . Kein Artikel handelt überhaupt nicht von ihm, sei nicht beunruhigt. Aber wir brauchen einen Brief, da er eng mit meiner Bibliothek verwandt ist. Dies ist der Buchstabe "D" - Prinzip der Abhängigkeitsinversion.

Das Prinzip der Abhängigkeitsinversion besagt:

  • Module der oberen Ebene sollten nicht von Modulen der unteren Ebene abhängen. Beide Modultypen sollten von Abstraktionen abhängen.
  • Abstraktionen sollten nicht von den Details abhängen. Details sollten von Abstraktionen abhängen.

Viele Menschen gehen fälschlicherweise davon aus, dass sie sich automatisch an dieses Prinzip halten, wenn sie Protokolle / Schnittstellen verwenden, aber dies ist nicht ganz richtig.

Die erste Aussage sagt etwas über Abhängigkeiten zwischen Modulen aus - Module müssen von Abstraktionen abhängen. Warten Sie, was ist Abstraktion? - Es ist besser, sich nicht zu fragen, was Abstraktion ist, sondern was Abstraktion ist. Das heißt, Sie müssen verstehen, was der Prozess ist, und das Ergebnis dieses Prozesses wird eine Abstraktion sein. Abstraktion ist eine Ablenkung des Erkenntnisprozesses von nicht wesentlichen Parteien, Eigenschaften und Beziehungen, um wesentliche, regelmäßige Zeichen hervorzuheben.

Das gleiche Objekt kann je nach Ziel unterschiedliche Abstraktionen haben. Beispielsweise hat die Maschine aus Sicht des Eigentümers die folgenden wichtigen Eigenschaften: Farbe, Eleganz, Bequemlichkeit. Aber aus Sicht des Mechanikers ist alles etwas anders: Marke, Modell, Modifikation, Kilometerstand, Unfallbeteiligung. Es wurden gerade zwei verschiedene Abstraktionen für ein Objekt benannt - die Maschine.

Beachten Sie, dass es in Swift üblich ist, Protokolle für Abstraktionen zu verwenden, dies ist jedoch keine Voraussetzung. Niemand macht sich die Mühe, eine Klasse zu erstellen, eine Reihe öffentlicher Methoden daraus zuzuweisen und Implementierungsdetails privat zu lassen. In Bezug auf die Abstraktion ist nichts kaputt. Wir müssen uns an die wichtige These erinnern: „Abstraktion ist nicht an die Sprache gebunden“ - dies ist ein Prozess, der ständig in unserem Kopf stattfindet, und wie dies auf den Code übertragen wird, ist nicht so wichtig. Hier können wir auch die Kapselung als Beispiel dafür erwähnen, was mit der Sprache verbunden ist. Jede Sprache hat ihre eigenen Mittel, um sie bereitzustellen. In Swift sind dies Klassen, Zugriffsfelder und Protokolle. auf Obj-C-Schnittstellen, Protokollen und Trennung von h- und m-Dateien.

Die zweite Aussage ist interessanter, da sie ignoriert oder missverstanden wird. Es geht um die Interaktion von Abstraktionen mit Details, und was sind Details? Es gibt ein Missverständnis, dass die Details Klassen sind, die Protokolle implementieren - ja, das ist wahr, aber nicht vollständig. Sie müssen verstehen, dass die Details nicht an Programmiersprachen gebunden sind - die C-Sprache hat weder Protokolle noch Klassen, aber dieses Prinzip wirkt auch darauf ein. Es fällt mir schwer, theoretisch zu erklären, was der Haken ist, deshalb werde ich zwei Beispiele nennen und dann versuchen zu beweisen, warum das zweite Beispiel korrekter ist.

Angenommen, es gibt ein Klassenauto und einen Klassenmotor. Es ist so passiert, dass wir sie anschließen müssen - die Maschine enthält einen Motor. Als kompetente Programmierer wählen wir die Protokoll-Engine aus, implementieren das Protokoll und übergeben die Implementierung des Protokolls an die Maschinenklasse. Alles scheint gut und richtig zu sein - jetzt können Sie die Motorimplementierung einfach ersetzen und nicht glauben, dass etwas kaputt geht. Als nächstes wird der Schaltung ein Motormechaniker hinzugefügt. Er interessiert sich für den Motor ganz andere Eigenschaften als das Auto. Wir erweitern das Protokoll und jetzt enthält es eine größere Anzahl von Funktionen als ursprünglich. Die Geschichte wird für den Besitzer des Autos, für die Fabrik, die Motoren usw. produziert, wiederholt.

Keine Umkehrung

Aber wo ist der Fehler in der Argumentation? Das Problem ist, dass die beschriebene Verbindung trotz der Verfügbarkeit von Protokollen tatsächlich ein "Detail" ist - ein "Detail". Genauer gesagt, in welchem ​​Namen und wo sich das Protokoll befindet, befindet sich die Engine.

Betrachten Sie nun die richtige andere Option.

Nach wie vor gibt es zwei Klassen - Motor und Auto. Nach wie vor müssen sie verbunden sein. Aber jetzt kündigen wir das Protokoll "Car Engine" oder "Heart of a Car" an. Wir setzen nur die Eigenschaften ein, die das Auto vom Motor benötigt. Und wir platzieren das Protokoll nicht neben seiner "Engine" -Implementierung, sondern neben der Maschine. Wenn wir einen Mechaniker benötigen, müssen wir ein anderes Protokoll erstellen und es in der Engine implementieren. Es scheint, dass sich nichts geändert hat, aber der Ansatz ist radikal anders - die Frage ist nicht so sehr in den Namen, sondern darin, wem die Protokolle gehören und was das Protokoll ist - eine „Abstraktion“ oder ein „Detail“.

Inversion ist

Lassen Sie uns nun eine Analogie zu einem anderen Fall ziehen, da diese Argumente möglicherweise nicht offensichtlich sind.

Es gibt ein Backend und einige Funktionen werden benötigt. Das Backend bietet uns eine große Methode, die eine Reihe von Daten enthält, und sagt: "Sie benötigen diese 3 Felder von 1000".

Kleine Geschichte
Viele können sagen, dass dies nicht geschieht. Und sie werden relativ richtig sein - es kommt vor, dass das Backend separat für die mobile Anwendung geschrieben wird. So kam es, dass ich für ein Unternehmen arbeitete, bei dem Backend ein Service mit einer 10-jährigen Geschichte ist, der unter anderem an die staatliche API gebunden ist. Aus vielen Gründen war es für das Unternehmen nicht üblich, eine separate Methode für das Handy zu schreiben, und ich musste das verwenden, was war. Und es gab eine wunderbare Methode mit ungefähr hundert Parametern in der Wurzel, und einige von ihnen waren verschachtelte Wörterbücher. Stellen Sie sich nun 100 Parameter vor, von denen 20% verschachtelte Parameter haben, und in jedem verschachtelten gibt es weitere 20-30 Parameter, die alle dieselbe Verschachtelung haben. Ich erinnere mich nicht genau, aber die Anzahl der Parameter überschritt 800 für einfache Objekte und für komplexe Objekte könnte sie höher als 1000 sein.

Klingt nicht sehr gut, oder? Normalerweise schreibt das Backend eine Methode für bestimmte Aufgaben für das Frontend, und das Frontend ist der Kunde / Benutzer dieser Methoden. Hmm ... Aber wenn Sie darüber nachdenken, ist das Backend der Motor und das Frontend das Auto - die Maschine benötigt einige Motoreigenschaften, und nicht dem Motor müssen die Eigenschaften für das Auto gegeben werden. Warum schreiben wir die Protokoll-Engine trotzdem weiter und platzieren sie näher an der Implementierung der Engine und nicht der Maschine? Es geht nur um die Skalierung - in den meisten iOS-Programmen ist es sehr selten, dass die Funktionalität so stark erweitert werden muss, dass eine solche Lösung zum Problem wird.

Und was ist dann DI


Es gibt eine Substitution von Konzepten - DI ist keine Abkürzung für DIP, sondern eine völlig andere Abkürzung, obwohl sie sich sehr eng mit DIP überschneidet. DI ist eine Abhängigkeitsinjektion oder Abhängigkeitsinjektion, keine Inversion. Inversion spricht darüber, wie Klassen und Protokolle miteinander interagieren sollen, und die Implementierung zeigt Ihnen, woher Sie sie beziehen können. Im Allgemeinen können Sie es auf verschiedene Arten implementieren - beginnend dort, wo die Abhängigkeiten auftreten: Konstruktor, Eigenschaft, Methode; endet mit denen, die sie erstellen und wie automatisiert dieser Prozess ist. Die Ansätze sind unterschiedlich, aber meiner Meinung nach sind Container für die Abhängigkeitsinjektion am bequemsten. Kurz gesagt, ihre ganze Bedeutung läuft auf eine einfache Regel hinaus: Wir sagen dem Container, wo und wie er implementiert werden soll, und danach wird alles unabhängig implementiert. Dieser Ansatz entspricht der „realen Implementierung von Abhängigkeiten“. In diesem Fall wissen die Klassen, in die die Abhängigkeiten eingebettet sind, nichts darüber, wie dies geschieht, dh sie sind passiv.

In vielen Sprachen wird für diese Implementierung der folgende Ansatz verwendet: In einzelnen Klassen / Dateien werden Implementierungsregeln mithilfe der Sprachsyntax beschrieben, wonach sie kompiliert und automatisch implementiert werden. Es gibt keine Magie - nichts passiert automatisch, nur die Bibliotheken sind eng in die grundlegenden Mittel der Sprache integriert und überlasten die Erstellungsmethoden. Für Swift / Obj-C wird daher allgemein angenommen, dass der Ausgangspunkt der UIViewController ist und Bibliotheken sich problemlos über das Storyboard in den erstellten ViewController integrieren können. Wenn Sie das Storyboard nicht verwenden, müssen Sie einen Teil der Arbeit mit Stiften erledigen.

Oh ja, ich hätte fast vergessen - die Antwort auf die Hauptfrage: "Warum brauchen wir das?" Zweifellos können Sie sich selbst um die Abhängigkeitsinjektion kümmern und alles mit Stiften verschreiben. Probleme treten jedoch auf, wenn die Diagramme groß werden - Sie müssen viele Verbindungen zwischen Klassen erwähnen, der Code wächst sehr stark. Daher nehmen Bibliotheken, die automatisch rekursiv (und sogar zyklisch) Abhängigkeiten implementieren, diese Sorgfalt auf sich und steuern als Bonus ihre Lebensdauer. Das heißt, die Bibliothek tut nichts anderes als das Natürliche - sie vereinfacht einfach das Leben des Entwicklers. Denken Sie nicht, dass Sie eine solche Bibliothek an einem Tag schreiben können - es ist eine Sache, alle Abhängigkeiten für einen bestimmten Fall mit einem Stift zu schreiben, es ist eine andere Sache, einem Computer beizubringen, universell und korrekt zu implementieren.

Bibliotheksgeschichte


Die Geschichte wäre nicht vollständig, wenn ich die Geschichte nicht kurz erzählen würde. Wenn Sie der Bibliothek aus der Beta-Version folgen, wird sie für Sie nicht so interessant sein, aber für diejenigen, die sie zum ersten Mal sehen, lohnt es sich zu verstehen, wie sie aussah und welche Ziele der Autor verfolgt hat (dh ich).
Die Bibliothek war mein zweites Projekt, für das ich mich aus Gründen der Selbstbildung entschied, in Swift zu schreiben. Vorher habe ich es geschafft, einen Logger zu schreiben, ihn aber nicht öffentlich zugänglich zu machen - es ist immer besser.

Aber mit DI ist die Geschichte interessanter. Als ich damit anfing, konnte ich auf Swift nur eine Bibliothek finden - Swinject. Zu dieser Zeit hatte sie 500 Sterne und Fehler, dass die Zyklen normalerweise nicht verarbeitet werden. Ich habe mir das alles angesehen und ... Mein Verhalten lässt sich am besten mit meinem Lieblingssatz „Und dann hat Ostap gelitten“ beschreiben. Ich habe 5-6 Sprachen durchgesehen, mir angesehen, was in diesen Sprachen vorkommt, Artikel zu diesem Thema gelesen und festgestellt, dass es besser geht. Und jetzt, nach fast drei Jahren, kann ich mit Zuversicht sagen, dass das Ziel erreicht wurde. Im Moment ist DITranquillity das Beste in meiner Weltanschauung.

Lassen Sie uns verstehen, was eine gute DI-Bibliothek ist:

  • Es sollte alle grundlegenden Implementierungen bereitstellen: Konstruktor, Eigenschaften, Methoden
  • Es sollte keinen Einfluss auf den Geschäftscode haben.
  • Sie sollte klar beschreiben, was schief gelaufen ist.
  • Sie muss im Voraus verstehen, wo Fehler vorliegen, nicht zur Laufzeit.
  • Es muss in grundlegende Tools (Storyboard) integriert sein.
  • Es sollte eine prägnante, prägnante Syntax haben.
  • Sie muss alles schnell und effizient erledigen.
  • (Optional) Es sollte hierarchisch sein

Diese Grundsätze versuche ich während der gesamten Entwicklung der Bibliothek einzuhalten.

Funktionen und Vorteile der Bibliothek


Zunächst ein Link zum Repository: github.com/ivlevAstef/DITranquillity

Der Hauptwettbewerbsvorteil, der für mich sehr wichtig ist, ist, dass die Bibliothek über Startfehler spricht. Nach dem Starten der Anwendung und dem Aufrufen der gewünschten Funktion werden alle vorhandenen und potenziellen Probleme gemeldet. Dies ist genau die Bedeutung des Namens der Bibliothek "ruhig" - tatsächlich garantiert die Bibliothek nach dem Start des Programms, dass alle erforderlichen Abhängigkeiten bestehen und es keine unlösbaren Zyklen gibt. An Stellen, an denen Unklarheiten bestehen, warnt die Bibliothek vor möglichen Problemen.

Es klingt gut für mich. Während der Ausführung des Programms treten keine Abstürze auf. Wenn der Programmierer etwas vergessen hat, wird dies sofort gemeldet.

Eine Protokollfunktion wird verwendet, um die Probleme zu beschreiben, die ich sehr empfehlen kann. Die Protokollierung hat 4 Ebenen: Fehler, Warnung, Info, ausführlich. Die ersten drei sind sehr wichtig. Letzteres ist nicht so wichtig - er schreibt alles, was passiert - welches Objekt registriert wurde, welches Objekt eingeführt wurde, welches Objekt erstellt wurde usw.

Dies ist jedoch nicht alles, was die Bibliothek zu bieten hat:

  • Volle Thread-Sicherheit - jede Operation kann von jedem Thread aus ausgeführt werden und alles wird funktionieren. Die meisten Leute brauchen dies nicht, daher wurde im Hinblick auf die Thread-Sicherheit daran gearbeitet, die Ausführungsgeschwindigkeit zu optimieren. Die Konkurrenzbibliothek fällt jedoch trotz der Versprechungen, wenn Sie sich gleichzeitig registrieren und ein Objekt erhalten
  • Schnelle Ausführungsgeschwindigkeit. Auf einem realen Gerät ist DITranquillity doppelt so schnell wie sein Konkurrent. Auf dem Simulator ist die Ausführungsgeschwindigkeit nahezu gleich. Test Link
  • Geringe Größe - Die Bibliothek wiegt weniger als Swinject + SwinjectStoryboad + SwinjectAutoregistration, übertrifft jedoch dieses Paket an Funktionen
  • Eine prägnante, prägnante Notiz, obwohl sie süchtig macht
  • Hierarchie. Für große Projekte, die aus vielen Modulen bestehen, ist dies ein sehr großes Plus, da die Bibliothek die erforderlichen Klassen anhand der Entfernung vom aktuellen Modul finden kann. Das heißt, wenn Sie in jedem Modul eine eigene Implementierung eines Protokolls haben, erhalten Sie in jedem Modul ohne Aufwand die gewünschte Implementierung

Demonstration


Und so fangen wir an. Als letztes Mal wird das Projekt berücksichtigt: SampleHabr . Ich habe speziell nicht begonnen, das Beispiel zu ändern - Sie können also vergleichen, wie sich alles geändert hat. Das Beispiel zeigt viele Funktionen der Bibliothek.

Nur für den Fall, dass es kein Missverständnis gibt, da das Projekt angezeigt wird, verwendet es viele Funktionen. Aber niemand stört sich daran, die Bibliothek auf vereinfachte Weise zu nutzen - heruntergeladen, einen Container erstellt, einige Klassen registriert, den Container verwenden.

Zuerst müssen wir ein Framework erstellen (optional):

public class AppFramework: DIFramework { //   public static func load(container: DIContainer) { //     } } 

Erstellen Sie zu Beginn des Programms Ihren eigenen Container mit diesem Framework:

 let container = DIContainer() //   container.append(framework: AppFramework.self) //     . //          ifdef DEBUG      ,         ,     . if !container.validate() { fatalError() } 

Storyboard


Als nächstes müssen Sie einen Basisbildschirm erstellen. Normalerweise werden dafür Storyboards verwendet, und in diesem Beispiel werde ich es verwenden, aber niemand stört sich daran, UIViewController zu verwenden.

Zunächst müssen wir ein Storyboard registrieren. Erstellen Sie dazu ein „Teil“ (optional - Sie können den gesamten Code in das Framework schreiben) mit dem darin registrierten Storyboard:

 import DITranquillity class AppPart: DIPart { static func load(container: DIContainer) { container.registerStoryboard(name: "Main", bundle: nil) .lifetime(.single) //   -    . } } 


Und fügen Sie AppFramework einen Teil hinzu:
 container.append(part: AppPart.self) 

Wie Sie sehen können, verfügt die Bibliothek über eine praktische Syntax zum Registrieren von Storyboard, und ich empfehle dringend, sie zu verwenden. Im Prinzip können Sie äquivalenten Code ohne diese Methode schreiben, dieser ist jedoch größer und kann StoryboardReferences nicht unterstützen. Das heißt, dieses Storyboard funktioniert nicht von einem anderen.

Jetzt müssen Sie nur noch ein Storyboard erstellen und den Startbildschirm anzeigen. Dies erfolgt in AppDelegate nach Überprüfung des Containers:

 window = UIWindow(frame: UIScreen.main.bounds) ///  Storyboard let storyboard: UIStoryboard = container.resolve(name: "Main") window!.rootViewController = storyboard.instantiateInitialViewController() window!.makeKeyAndVisible() 

Das Erstellen eines Storyboards mithilfe einer Bibliothek ist nicht viel komplizierter als gewöhnlich. In diesem Beispiel könnte der Name übersehen werden, da wir nur ein Storyboard haben - die Bibliothek hätte vermutet, dass Sie daran gedacht haben. In einigen Projekten gibt es jedoch viele Storyboards. Verpassen Sie den Namen also nicht noch einmal.

Presenter und ViewController


Gehen Sie zum Bildschirm selbst. Wir werden das Projekt nicht mit komplexen Architekturen laden, sondern das übliche MVP verwenden. Außerdem bin ich so faul, dass ich kein Protokoll für einen Moderator erstellen werde. Das Protokoll wird etwas später für eine andere Klasse erstellt. Hier ist es wichtig zu zeigen, wie Presenter und ViewController registriert und verknüpft werden.

Fügen Sie dazu AppPart den folgenden Code hinzu:

 container.register(YourPresenter.init) container.register(YourViewController.self) .injection(\.presenter) //   

Mit diesen drei Zeilen können wir zwei Klassen registrieren und eine Verbindung zwischen ihnen herstellen.

Neugierige mögen sich fragen - warum ist die Syntax, die Swinject in einer separaten Bibliothek hat, die wichtigste im Projekt? Die Antwort liegt in den Zielen - dank dieser Syntax speichert die Bibliothek alle Links im Voraus, anstatt sie zur Laufzeit zu berechnen. Mit dieser Syntax können Sie auf viele Funktionen zugreifen, die anderen Bibliotheken nicht zur Verfügung stehen.

Wir starten die Anwendung und alles funktioniert, alle Klassen werden erstellt.

Daten


Nun müssen wir eine Klasse und ein Protokoll hinzufügen, um Daten vom Server zu empfangen:

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

Aus Gründen der Schönheit erstellen wir eine separate ServerPart DI-Klasse für den Server, in der wir sie registrieren. Ich möchte Sie daran erinnern, dass dies nicht erforderlich ist und direkt im Container registriert werden kann, aber wir suchen nicht nach einfachen Wegen :)

 import DITranquillity class ServerPart: DIPart { static func load(container: DIContainer) { container.register{ ServerImpl(domain: "https://github.com/") } .as(check: Server.self){$0} .lifetime(.single) } } 

In diesem Code ist nicht alles so transparent wie in den vorherigen und muss geklärt werden. Zunächst wird im Funktionsregister eine Klasse mit einem übergebenen Parameter erstellt.

Zweitens gibt es die Funktion "as" - sie besagt, dass auf die Klasse ein anderer Typ zugreifen kann - das Protokoll. Das seltsame Ende dieser Operation in Form von "{$ 0}" ist Teil des Namens "check:". Das heißt, dieser Code stellt sicher, dass ServerImpl ein Nachfolger von Server ist. Es gibt jedoch eine andere Syntax: `as (Server.self)`, die dasselbe tut, jedoch ohne Überprüfung. Um zu sehen, was der Compiler in beiden Fällen ausgibt, können Sie die Protokollimplementierung entfernen.

Es kann mehrere "as" -Funktionen geben - dies bedeutet, dass der Typ unter einem dieser Namen verfügbar ist. Bitte beachten Sie, dass dies eine einzelne Registrierung ist. Wenn die Klasse ein Singleton ist, ist dieselbe Instanz für jeden angegebenen Typ verfügbar.

Wenn Sie sich vor der Möglichkeit schützen möchten, eine Klasse nach Implementierungstyp zu erstellen, oder sich noch nicht an diese Syntax gewöhnt haben, können Sie im Prinzip schreiben:

 container.register{ ServerImpl(domain: "https://github.com/") as Server } 

Dies ist ein Äquivalent, jedoch ohne die Möglichkeit, mehrere separate Typen anzugeben.

Jetzt können Sie den Server in Presenter implementieren. Dazu reparieren wir Presenter so, dass er Server akzeptiert:

  class YourPresenter { init(server: Server) { ... } } 

Wir starten das Programm und es fällt auf die "validate" -Funktionen in AppDelegate mit der Meldung, dass der Typ "Server" nicht gefunden wurde, aber von "YourPresenter" benötigt wird. Was ist los? Bitte beachten Sie, dass der Fehler zu Beginn der Programmausführung und nicht nachträglich aufgetreten ist. Und der Grund ist ganz einfach: Sie haben vergessen, "ServerPart" zum "AppFramework" hinzuzufügen:

 container.append(part: ServerPart.self) 

Wir fangen an - alles funktioniert.

Logger


Zuvor gab es eine Bekanntschaft mit Möglichkeiten, die nicht sehr beeindruckend sind und die viele haben. Jetzt wird demonstriert, dass andere Bibliotheken auf Swift nicht wissen, wie.

Unter dem Logger wurde ein separates Projekt erstellt.

Lassen Sie uns zunächst verstehen, was ein Logger sein wird. Zu Bildungszwecken werden wir kein ausgetrickstes System erstellen, daher ist der Logger ein Protokoll mit einer Methode und mehreren Implementierungen:

 public protocol Logger { func log(_ msg: String) } class ConsoleLogger: Logger { func log(_ msg: String) { ... } } class FileLogger: Logger { init(file: String) { ... } func log(_ msg: String) { ... } } class ServerLogger: Logger { init(server: String) { ... } func log(_ msg: String) { ... } } class MainLogger: Logger { init(loggers: [Logger]) { ... } func log(_ msg: String) { ... } } 

Insgesamt haben wir:

  • Öffentliches Protokoll
  • 3 verschiedene Logger-Implementierungen, von denen jede an einen anderen Ort schreibt
  • Ein zentraler Logger, der die Protokollierungsfunktion für alle anderen aufruft

Das Projekt hat "LoggerFramework" und "LoggerPart" erstellt. Ich werde ihren Code nicht ausschreiben, aber ich werde nur die Interna von `LoggerPart` ausschreiben:

 container.register{ ConsoleLogger() } .as(Logger.self) .lifetime(.single) container.register{ FileLogger(file: "file.log") } .as(Logger.self) .lifetime(.single) container.register{ ServerLogger(server: "http://server.com/") } .as(Logger.self) .lifetime(.single) container.register{ MainLogger(loggers: many($0)) } .as(Logger.self) .default() .lifetime(.single) 

Wir haben bereits die ersten 3 Registrierungen gesehen und die letzte wirft Fragen auf.

Ein Parameter wird an den Eingang übergeben. Eine ähnliche wurde bereits bei der Erstellung des Präsentators gezeigt, obwohl es einen abgekürzten Datensatz gab - die "init" -Methode wurde nur verwendet, aber niemand stört sich daran, so zu schreiben:

 container.register { YourPresenter(server: $0) } 

Wenn es mehrere Parameter gäbe, könnte man "$ 1", "$ 2", "$ 3" usw. verwenden. bis 16.

Dieser Parameter ruft jedoch die Funktion "many" auf. Und hier beginnt der Spaß. Es gibt zwei Modifikatoren "many" und "tag" in der Bibliothek.
Versteckter Text
Es gibt einen dritten "arg" -Modifikator, der jedoch nicht sicher ist
Der Modifikator "viele" besagt, dass Sie alle Objekte abrufen müssen, die dem gewünschten Typ entsprechen. In diesem Fall wird das Logger-Protokoll erwartet, sodass alle Klassen, die von diesem Protokoll erben, gefunden und erstellt werden, mit einer Ausnahme - selbst, dh rekursiv. Es wird sich während der Initialisierung nicht selbst erstellen, obwohl es dies sicher tun kann, wenn es über eine Eigenschaft implementiert wird.

Das Tag ist wiederum ein separater Typ, der sowohl während der Verwendung als auch während der Registrierung angegeben werden muss. Das heißt, Tags sind zusätzliche Kriterien, wenn nicht genügend Grundtypen vorhanden sind.

Sie können mehr darüber lesen: Modifikatoren

Das Vorhandensein von Modifikatoren, insbesondere von "vielen", macht die Bibliothek besser als andere. Beispielsweise können Sie das Observer-Muster auf einer völlig anderen Ebene implementieren. Aufgrund dieser 4 Buchstaben war es im Projekt möglich, 30-50 Codezeilen von jedem Beobachter im Projekt zu entfernen und das Problem mit der Frage zu lösen - wo und wann Objekte zum Observable hinzugefügt werden sollten. Klares Geschäft ist nicht die einzige Anwendung, aber von Bedeutung.

Nun, wir beenden die Präsentation der Funktionen mit der Einführung eines Loggers in YourPresenter:

 container.register(YourPresenter.init) .injection { $0.logger = $1 } 

Hier ist es zum Beispiel etwas anders geschrieben als zuvor - dies geschieht für ein Beispiel einer anderen Syntax.

Bitte beachten Sie, dass die Logger-Eigenschaft optional ist:

 internal var logger: Logger? 

Und dies erscheint nicht in der Syntax der Bibliothek. Im Gegensatz zur ersten Version sehen jetzt alle Operationen für den üblichen Typ, optional und erzwungen optional, gleich aus. Darüber hinaus ist die Logik im Inneren anders - wenn der Typ optional ist und nicht im Container registriert ist, stürzt das Programm nicht ab, sondern setzt die Ausführung fort.

Zusammenfassung


Die Ergebnisse sind ähnlich wie beim letzten Mal, nur die Syntax ist kürzer und funktionaler geworden.

Was wurde überprüft:



Was kann die Bibliothek noch tun:



Pläne


Zunächst ist geplant, das Diagramm in der Kompilierungsphase zu überprüfen, dh enger in den Compiler zu integrieren. Es gibt eine vorläufige Implementierung mit SourceKitten, aber eine solche Implementierung hat ernsthafte Schwierigkeiten mit der Typinferenz. Daher ist geplant, auf Ast-Dump umzusteigen - in Swift5 wurde sie an großen Projekten arbeiten. An dieser Stelle möchte ich mich bei Nekitosss für den großen Beitrag in diese Richtung bedanken .

Zweitens möchte ich mich in Visualisierungsdienste integrieren. Dies wird ein etwas anderes Projekt sein, das jedoch eng mit der Bibliothek verbunden ist. Was ist der Punkt? Jetzt speichert die Bibliothek das gesamte Diagramm der Verbindungen, dh theoretisch kann alles, was in der Bibliothek registriert ist, als UML-Klassen- / Komponentendiagramm angezeigt werden. Und es wäre schön, dieses Diagramm manchmal zu sehen.

Diese Funktionalität ist in zwei Teilen geplant: Im ersten Teil können Sie eine API hinzufügen, um alle Informationen abzurufen, und im zweiten Teil sind bereits verschiedene Dienste integriert.

Die einfachste Option besteht darin, ein Diagramm mit Links in Form von Text anzuzeigen. Ich habe jedoch keine lesbaren Optionen gesehen. Wenn ja, schlagen Sie Optionen in den Kommentaren vor.

WatchOS - Ich selbst schreibe keine Projekte für sie. Für sein Leben schrieb er nur einmal und dann klein. Aber ich würde gerne eine enge Integration machen, wie beim Storyboard.

Das ist alles, danke für Ihre Aufmerksamkeit. Ich hoffe sehr auf Kommentare und Antworten auf die Umfrage.

Über mich
Ivlev Alexander Evgenievich - Senior / Teamleiter im iOS-Team. Ich arbeite seit 7 Jahren im Handel, unter iOS 4,5 Jahre - davor war ich C ++ - Entwickler. Aber die gesamte Programmiererfahrung beträgt mehr als 15 Jahre - in der Schule habe ich diese erstaunliche Welt kennengelernt und war so begeistert, dass es eine Zeit gab, in der ich Spiele , Essen, eine Toilette und einen Traum zum Schreiben von Code austauschte. Nach einem meiner Artikel kann man vermuten, dass ich eine ehemalige Olympiade bin - dementsprechend war es für mich nicht schwierig, kompetente Arbeit mit Grafiken zu schreiben. Spezialität - Informationsmesssysteme, und früher war ich von Multithreading und Parallelität besessen - ja, ich schreibe Code, in dem ich Annahmen und Fehler zu ähnlichen Themen mache, aber ich verstehe die Problembereiche und verstehe perfekt, wo und wo Sie den Mutex vernachlässigen können nicht wert.

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


All Articles