Meine Erfahrung von Fehlern
Fehlerliste
- Allmächtige Klasse MCManager
- Unsere Navigation zwischen den Bildschirmen erfinden
- Es gibt nie viel Vererbung
- Architektur aus eigener Produktion oder weiterhin Fahrräder herstellen
- MVVM mit Soul MVP
- Der zweite Versuch mit Navigation oder Router und der Krümmung der Navigation
- Hartnäckiger Manager
Viele Menschen, auch ich, schreiben, wie man in einer bestimmten Situation das Richtige tut, wie man Code richtig schreibt, wie man architektonische Lösungen anwendet usw. Aber ich möchte meine Erfahrungen darüber teilen, wie es falsch gemacht wurde und welche Schlussfolgerungen ich gezogen habe basierend auf seinen Fehlern gemacht. Höchstwahrscheinlich sind dies häufige Fehler aller, die dem Weg des Entwicklers folgen, oder vielleicht ist etwas Neues. Ich möchte nur meine Erfahrungen teilen und die Kommentare anderer Leute lesen.
Allmächtige Klasse MCManager
Nach dem ersten Jahr der Arbeit in der IT und insbesondere der iOS-Entwicklung entschied ich, dass ich bereits Architekt genug und bereit war, etwas zu erstellen. Schon damals habe ich intuitiv verstanden, dass es notwendig ist, die Geschäftslogik von der Präsentationsschicht zu trennen. Aber die Qualität meiner Vorstellung davon war weit von der Realität entfernt.
Ich zog an einen neuen Arbeitsplatz, wo ich beauftragt wurde, eigenständig eine neue Funktion für ein bestehendes Projekt zu entwickeln. Es war ein Analogon zur Aufnahme von Videos auf Instagram, bei der die Aufnahme durchgeführt wird, während der Benutzer seinen Finger auf der Schaltfläche hält, und dann mehrere Fragmente des Videos miteinander verbunden werden. Zunächst wurde beschlossen, diese Funktion als separates Projekt oder vielmehr in Form eines Beispiels zu erstellen. So wie ich es verstehe, beginnt die Quelle meiner architektonischen Probleme, die mehr als ein Jahr dauerten.
In Zukunft hat sich dieses Beispiel zu einer vollwertigen Anwendung zum Aufnehmen und Bearbeiten von Videos entwickelt. Es ist lustig, dass die Probe ursprünglich einen Namen hatte, von dessen Abkürzung das MC-Präfix gesammelt wurde. Das Projekt wurde zwar bald umbenannt, aber das Präfix gemäß der Namenskonvention in Objective-C blieb MC erhalten. So wurde die allmächtige MCManager-Klasse geboren.

Da es sich um ein Beispiel handelte und die Funktionalität zunächst einfach war, entschied ich, dass eine Managerklasse ausreichen würde. Wie bereits erwähnt, umfasste die Funktionalität das Aufzeichnen eines Videofragments mit Start / Stopp-Optionen und das weitere Kombinieren dieser Fragmente zu einem ganzen Video. Und in diesem Moment kann ich meinen ersten Fehler nennen - den Namen der MCManager-Klasse. MCManager, Karl! Was sollte ein Klassenname anderen Entwicklern über seinen Zweck, seine Fähigkeiten und seine Verwendung sagen? Richtig, absolut nichts! Und das steht im Anhang, dessen Name nicht einmal die Buchstaben M und seine Mutter C enthält. Obwohl dies nicht mein Hauptfehler ist, da die Klasse mit dem Partisanennamen alles getan hat, ist alles aus dem Wort absolut alles, was der Hauptfehler war.
Die Videoaufzeichnung ist ein kleiner Dienst. Die Verwaltung der Speicherung von Videodateien im Dateisystem ist der zweite und zusätzliche Dienst zum Kombinieren mehrerer Videos zu einem. Die Arbeit dieser drei unabhängigen Dienste wurde beschlossen, in einem Manager zu kombinieren. Die Idee war nobel, unter Verwendung des Fassadenmusters eine einfache Schnittstelle für die Geschäftslogik zu erstellen und alle unnötigen Details über das Zusammenspiel verschiedener Komponenten zu verbergen. Selbst der Name einer solchen Fassadenklasse ließ in der Anfangsphase keinen Verdacht aufkommen, insbesondere in der Stichprobe.
Aber der Kunde mochte die Demo und bald verwandelte sich das Beispiel in eine vollwertige Anwendung. Sie können rechtfertigen, dass nicht genügend Zeit für das Refactoring vorhanden war, dass der Kunde den Arbeitscode nicht wiederholen wollte, aber ehrlich gesagt dachte ich in diesem Moment selbst, ich hätte eine hervorragende Architektur festgelegt. In Wahrheit war die Idee, Geschäftslogik und Präsentation zu trennen, ein Erfolg. Die Architektur war eine Klasse von Singleton MCManager, die die Fassade für ein paar Dutzend Services und andere Manager war. Ja, es war auch ein Singleton, der in allen Ecken der Anwendung verfügbar war.
Man kann bereits das Ausmaß der gesamten Katastrophe verstehen. Eine Klasse mit mehreren tausend Codezeilen, die schwer zu lesen und zu pflegen ist. Ich schweige bereits über die Möglichkeit, einzelne Funktionen hervorzuheben, um sie auf eine andere Anwendung zu übertragen, was in der mobilen Entwicklung durchaus üblich ist.
Die Schlussfolgerungen, die ich nach einer Weile für mich selbst gezogen habe, sind nicht, universelle Klassen mit obskuren Namen zu schaffen. Mir wurde klar, dass Logik in Teile zerlegt werden muss und nicht für alles eine universelle Schnittstelle schaffen muss. Tatsächlich war dies ein Beispiel dafür, was passieren würde, wenn Sie eines der SOLID-Prinzipien, das Prinzip der Schnittstellentrennung, nicht einhalten.
Unsere Navigation zwischen den Bildschirmen erfinden
Die Trennung von Logik und Schnittstelle ist nicht das einzige Problem, das mich über das obige Projekt beunruhigt hat. Ich werde nicht sagen, dass ich in diesem Moment den Bildschirmcode und den Navigationscode trennen wollte, aber es stellte sich heraus, dass ich mein Fahrrad für die Navigation entwickelt hatte.

Die Stichprobe hatte nur drei Bildschirme: ein Menü mit einer Tabelle mit aufgezeichneten Videos, einen Aufzeichnungsbildschirm und einen Nachbearbeitungsbildschirm. Um nicht zu beachten, dass der Navigationsstapel doppelte ViewController enthält, habe ich beschlossen, den UINavigationController nicht zu verwenden. Ich habe den RootViewcontroller hinzugefügt. Aufmerksame Leser haben bereits vermutet, dass es sich um den MCRootViewController handelt, der in den Projekteinstellungen als Hauptversion festgelegt wurde. Gleichzeitig war der Root-Controller nicht einer der Anwendungsbildschirme, sondern präsentierte lediglich den gewünschten UIViewController. Als ob dies nicht genug wäre, war der Root-Controller auch ein Delegat aller vertretenen Controller. Infolgedessen gab es zu jedem Zeitpunkt nur zwei VC in der Hierarchie, und die gesamte Navigation wurde unter Verwendung des Delegatenmusters implementiert.
So sah es aus: Jeder Bildschirm hatte ein eigenes Delegatenprotokoll, in dem Navigationsmethoden angegeben waren, und der Root-Controller implementierte diese Methoden und änderte die Bildschirme. RootViewController dissmisil current controller, erstellte einen neuen und präsentierte ihn, während es möglich war, Informationen von einem Bildschirm auf einen anderen zu übertragen. Glücklicherweise gehörte die Geschäftslogik zur coolsten Singleton-Klasse, sodass auf keinem der Bildschirme etwas gespeichert war und schmerzlos zerstört werden konnte. Wieder wurde eine gute Absicht verwirklicht, obwohl die Erkenntnis auf beiden Beinen humpelte und manchmal stolperte.
Wie Sie vielleicht erraten haben, wurde die Methode aufgerufen, wenn Sie vom Videoaufzeichnungsbildschirm zurück zum Hauptmenü wechseln müssen:
- (void)cancel;
oder so ähnlich, und der Root-Controller erledigt bereits die ganze Drecksarbeit.
Am Ende wurde MCRootViewController ein Analogon zu MCManager, aber bei der Navigation zwischen den Bildschirmen wurden, wie mit dem Wachstum der Anwendung und dem Hinzufügen neuer Funktionen, neue Bildschirme hinzugefügt.
Die Fahrradfabrik funktionierte unaufhaltsam und ich ignorierte weiterhin Artikel über Architekturen mobiler Anwendungen. Aber ich habe nie die Idee aufgegeben, die Navigation von den Bildschirmen zu trennen.
Der Vorteil war, dass die Bildschirme unabhängig waren und wiederverwendet werden konnten, aber dies ist nicht genau. Zu den Nachteilen gehört jedoch die Schwierigkeit, solche Klassen aufrechtzuerhalten. Das Problem mit dem Fehlen eines Stapels von Bildschirmen, wenn Sie zurückkehren müssen, indem Sie durch zuvor ausgewählte Bildschirme scrollen. Die komplexe Logik des Übergangs zwischen Bildschirmen, der Root-Controller, wirkte sich auf einen Teil der Geschäftslogik aus, um einen neuen Bildschirm korrekt anzuzeigen.
Im Allgemeinen sollten Sie nicht die gesamte Navigation in der Anwendung auf diese Weise implementieren, da mein MCRootViewController gegen das Open-Closed-Prinzip verstößt. Es ist fast unmöglich zu erweitern, und alle Änderungen müssen ständig an der Klasse selbst vorgenommen werden.
Ich begann mehr über die Navigation zwischen Bildschirmen in einer mobilen Anwendung zu lesen und lernte Ansätze wie Router und Koordinator kennen. Ich werde etwas später über Router schreiben, da es etwas zu teilen gibt.
Es gibt nie viel Vererbung
Ich möchte auch nicht nur meine Perlen teilen, sondern auch die lustigen Ansätze und Lösungen anderer Leute, mit denen ich mich befassen musste. An derselben Stelle, an der ich meine Meisterwerke schuf, vertrauten sie mir eine einfache Aufgabe an. Die Aufgabe bestand darin, meinem Projekt einen Bildschirm aus einem anderen Projekt hinzuzufügen. Wie wir mit PM festgestellt haben, sollte dies nach einer flachen Analyse und ein wenig Überlegung zwei oder drei Stunden und nicht länger dauern, denn was daran falsch ist, müssen Sie Ihrer Anwendung lediglich eine vorgefertigte Bildschirmklasse hinzufügen. In der Tat wurde bereits alles für uns getan, wir müssen Strg + C und Strg + V tun. Hier nur eine kleine Nuance: Der Entwickler, der diese Anwendung geschrieben hat, hat die Vererbung wirklich geliebt.

Ich fand schnell den ViewController, den ich brauchte. Ich hatte Glück, dass es keine Trennung zwischen Logik und Präsentation gab. Es war ein guter alter Ansatz, als der Controller den gesamten erforderlichen Code enthielt. Ich kopierte es in mein Projekt und begann herauszufinden, wie es funktioniert. Und das erste, was ich entdeckt habe, ist, dass der Controller, den ich brauche, von einem anderen Controller erbt. Eine häufige Sache, ein ziemlich erwartetes Ereignis. Da ich nicht viel Zeit hatte, fand ich einfach die Klasse, die ich brauchte, und zog sie in mein Projekt. Nun sollte es funktionieren, dachte ich, und ich hatte mich noch nie so geirrt!
Die Klasse, die ich brauchte, hatte nicht nur viele Variablen von benutzerdefinierten Klassen, die auch in mein Projekt kopiert werden mussten, sodass jede von ihnen etwas erbte. Im Gegenzug waren die Basisklassen entweder geerbt oder enthielten Felder mit benutzerdefinierten Typen, die, wie viele vermutet haben, etwas geerbt haben, und dies war leider nicht NSObject, UIViewController oder UIView. Somit ist ein gutes Drittel des unnötigen Projekts im Projekt auf mich migriert.
Da nicht viel Zeit für die Ausführung dieser Aufgabe erwartet wurde, sah ich keinen anderen Ausweg, wie ich einfach die erforderlichen Klassen hinzufügen konnte, die xCode benötigte, um mein Projekt schmerzlos zu starten. Infolgedessen zogen sich zwei oder drei Stunden etwas hin, als ich mich am Ende wie ein wahrer Bildhauer in das gesamte Netz der Vererbungshierarchie vertiefen musste, um den Überschuss abzuschneiden.
Infolgedessen kam ich zu dem Schluss, dass alle guten Dinge in Maßen sein sollten, selbst eine so „wunderbare“ Sache wie das Erbe. Dann begann ich die Nachteile der Vererbung zu verstehen. Ich kam zu dem Schluss, wenn ich wiederverwendbare Module herstellen möchte, sollte ich sie unabhängiger machen.
Architektur aus eigener Produktion oder weiterhin Fahrräder herstellen
Als ich an einen neuen Arbeitsplatz zog und ein neues Projekt startete, berücksichtigte ich alle verfügbaren Erfahrungen in der Gestaltung von Architektur und schuf weiter. Natürlich ignorierte ich weiterhin die bereits erfundenen Architekturen, hielt mich aber gleichzeitig beharrlich an das Prinzip „Teilen und Erobern“.
Es dauerte nicht lange, bis Swift aufkam, und so ging ich auf die Möglichkeiten von Objective-c ein. Ich habe mich für die Abhängigkeitsinjektion mit den Sprachfunktionen entschieden. Ich habe mich vom lib-Erweiterungstool inspirieren lassen und kann mich nicht einmal an seinen Namen erinnern.
Das Fazit lautet: In der BaseViewController-Basisklasse habe ich ein BaseViewModel-Klassenfeld hinzugefügt. Dementsprechend habe ich für jeden Bildschirm meinen eigenen Controller erstellt, der die grundlegenden geerbt hat, und ein Protokoll hinzugefügt, damit der Controller mit dem viewModel interagieren kann. Dann kam die Magie. Ich habe die viewModel-Eigenschaften neu definiert und Unterstützung für das gewünschte Protokoll hinzugefügt. Im Gegenzug habe ich eine neue ViewModel-Klasse für einen bestimmten Bildschirm erstellt, der dieses Protokoll implementiert hat. Als Ergebnis überprüfte ich im BaseViewController in der viewDidLoad-Methode den Typ des Protokolls des Modells, überprüfte die Liste aller Nachkommen von BaseViewModel, fand die benötigte Klasse und erstellte das viewModel des benötigten Typs.
Grundlegendes ViewController-Beispiel #import <UIKit/UIKit.h> // MVC model #import "BaseMVCModel.h" @class BaseViewController; @protocol BaseViewControllerDelegate <NSObject> @required - (void)backFromNextViewController:(BaseViewController *)aNextViewController withOptions:(NSDictionary *)anOptionsDictionary; @end @interface BaseViewController : UIViewController <BaseViewControllerDelegate> @property (nonatomic, weak) BaseMVCModel *model; @property (nonatomic, assign) id<BaseViewControllerDelegate> prevViewController; - (void)backWithOptions:(NSDictionary *)anOptionsDictionary; + (void)setupUIStyle; @end import "BaseViewController.h" // Helpers #import "RuntimeHelper.h" @interface BaseViewController () @end @implementation BaseViewController + (void)setupUIStyle { } #pragma mark - #pragma mark Life cycle - (void)viewDidLoad { [super viewDidLoad]; self.model = [BaseMVCModel getModel:FindPropertyProtocol(@"model", [self class])]; } #pragma mark - #pragma mark Navigation - (void)backWithOptions:(NSDictionary *)anOptionsDictionary { if (self.prevViewController) { [self.prevViewController performSelector:@selector(backFromNextViewController:withOptions:) withObject:self withObject:anOptionsDictionary]; } } #pragma mark - #pragma mark Seque - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.destinationViewController isKindOfClass:[BaseViewController class]] { ((BaseViewController *)segue.destinationViewController).prevViewController = self; } } #pragma mark - #pragma mark BaseViewControllerDelegate - (void)backFromNextViewController:(BaseViewController *)aNextViewController withOptions:(NSDictionary *)anOptionsDictionary { [self doesNotRecognizeSelector:_cmd]; } @end
Grundlegendes ViewModel-Beispiel #import <Foundation/Foundation.h> @interface BaseMVCModel : NSObject @property (nonatomic, assign) id delegate; + (id)getModel:(NSString *)someProtocol; @end #import "BaseMVCModel.h" // IoC #import "IoCContainer.h" @implementation BaseMVCModel + (id)getModel:(NSString *)someProtocol { return [[IoCContainer sharedIoCContainer] getModel:NSProtocolFromString(someProtocol)]; } @end
Hilfsklassen #import <Foundation/Foundation.h> @interface IoCContainer : NSObject + (instancetype)sharedIoCContainer; - (id)getModel:(Protocol *)someProtocol; @end #import "IoCContainer.h" // Helpers #import "RuntimeHelper.h" // Models #import "BaseMVCModel.h" @interface IoCContainer () @property (nonatomic, strong) NSMutableSet *models; @end @implementation IoCContainer #pragma mark - #pragma mark Singleton + (instancetype)sharedIoCContainer { static IoCContainer *_sharedIoCContainer = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _sharedIoCContainer = [IoCContainer new]; }); return _sharedIoCContainer; } - (id)getModel:(Protocol *)someProtocol { if (!someProtocol) { return [BaseMVCModel new]; } NSArray *modelClasses = ClassGetSubclasses([BaseMVCModel class]); __block Class currentClass = NULL; [modelClasses enumerateObjectsUsingBlock:^(Class class, NSUInteger idx, BOOL *stop) { if ([class conformsToProtocol:someProtocol]) { currentClass = class; } }]; if (currentClass == nil) { return [BaseMVCModel new]; } __block BaseMVCModel *currentModel = nil; [self.models enumerateObjectsUsingBlock:^(id model, BOOL *stop) { if ([model isKindOfClass:currentClass]) { currentModel = model; } }]; if (!currentModel) { currentModel = [currentClass new]; [self.models addObject:currentModel]; } return currentModel; } - (NSMutableSet *)models { if (!_models) { _models = [NSMutableSet set]; } return _models; } @end #import <Foundation/Foundation.h> NSString * FindPropertyProtocol(NSString *propertyName, Class class); NSArray * ClassGetSubclasses(Class parentClass); #import "RuntimeHelper.h" #import <objc/runtime.h> #pragma mark - #pragma mark Functions NSString * FindPropertyProtocol(NSString *aPropertyName, Class class) { unsigned int propertyCount; objc_property_t *properties = class_copyPropertyList(class, &propertyCount); for (unsigned int i = 0; i < propertyCount; i++) { objc_property_t property = properties[i]; const char *propertyName = property_getName(property); if ([@(propertyName) isEqualToString:aPropertyName]) { const char *attrs = property_getAttributes(property); NSString* propertyAttributes = @(attrs); NSScanner *scanner = [NSScanner scannerWithString: propertyAttributes]; [scanner scanUpToString:@"<" intoString:NULL]; [scanner scanString:@"<" intoString:NULL]; NSString* protocolName = nil; [scanner scanUpToString:@">" intoString: &protocolName]; return protocolName; } } return nil; } NSArray * ClassGetSubclasses(Class parentClass) { int numClasses = objc_getClassList(NULL, 0); Class *classes = NULL; classes = (Class *)malloc(sizeof(Class) * numClasses); numClasses = objc_getClassList(classes, numClasses); NSMutableArray *result = [NSMutableArray array]; for (NSInteger i = 0; i < numClasses; i++) { Class superClass = classes[i]; do { superClass = class_getSuperclass(superClass); } while(superClass && superClass != parentClass); if (superClass == nil) { continue; } [result addObject:classes[i]]; } free(classes); return result; }
Beispiel für einen Anmeldebildschirm #import "BaseViewController.h" @protocol LoginProtocol <NSObject> @required - (void)login:(NSString *)aLoginString password:(NSString *)aPasswordString completionBlock:(DefaultCompletionBlock)aCompletionBlock; @end @interface LoginVC : BaseViewController @end #import "LoginVC.h" #import "UIViewController+Alert.h" #import "UIViewController+HUD.h" @interface LoginVC () @property id<LoginProtocol> model; @property (weak, nonatomic) IBOutlet UITextField *emailTF; @property (weak, nonatomic) IBOutlet UITextField *passTF; @end @implementation LoginVC @synthesize model = _model; #pragma mark - #pragma mark IBActions - (IBAction)loginAction:(id)sender { [self login]; } #pragma mark - #pragma mark UITextFieldDelegate - (BOOL)textFieldShouldReturn:(UITextField *)textField { if (textField == self.emailTF) { [self.passTF becomeFirstResponder]; } else { [self login]; } return YES; } #pragma mark - #pragma mark Login - (void)login { NSString *email = self.emailTF.text; NSString *pass = self.passTF.text; if (email.length == 0 || pass.length == 0) { [self showAlertOkWithMessage:@"Please, input info!"]; return; } __weak __typeof(self)weakSelf = self; [self showHUD]; [self.model login:self.emailTF.text password:self.passTF.text completionBlock:^(BOOL isDone, NSError *anError) { [weakSelf hideHUD]; if (isDone) { [weakSelf backWithOptions:nil]; } }]; } @end
Auf diese einfache Weise habe ich viewModel nur schleppend initialisiert und die Ansicht über Protokolle schlecht mit Modellen verbunden. Bei alledem wusste ich zu diesem Zeitpunkt noch nichts über die MVP-Architektur, obwohl sich etwas Ähnliches über mir abzeichnete.
Die Navigation zwischen den Bildschirmen lag im Ermessen von „viewModel“, da ich dem Controller einen schwachen Link hinzugefügt habe.
Wenn ich mich jetzt an diese Implementierung erinnere, kann ich nicht sicher sagen, dass alles schlecht war. Die Idee, Ebenen zu trennen, war ein Erfolg. Der Moment des Erstellens und Zuweisen von Modellen zur Steuerung wurde vereinfacht.
Aber für mich selbst habe ich mich entschlossen, mehr über vorgefertigte Ansätze und Architekturen zu lernen, da ich mich bei der Entwicklung einer Anwendung mit meiner eigenen Architektur mit vielen Nuancen auseinandersetzen musste. Zum Beispiel Wiederverwendung von Bildschirmen und Modellen, Vererbung, komplexe Übergänge zwischen Bildschirmen. In diesem Moment schien mir viewModel Teil der Geschäftslogik zu sein, obwohl ich jetzt verstehe, dass es immer noch eine Präsentationsschicht ist. Ich habe während dieses Experiments großartige Erfahrungen gemacht.
MVVM mit Soul MVP
Nachdem ich bereits Erfahrungen gesammelt hatte, entschied ich mich, eine bestimmte Architektur für mich zu wählen und dieser zu folgen, anstatt Fahrräder zu erfinden. Ich begann mehr über Architekturen zu lesen, mich eingehend mit den damals beliebten Themen zu befassen und entschied mich für MVVM. Ehrlich gesagt habe ich das Wesentliche nicht sofort verstanden, aber ich habe es gewählt, weil mir der Name gefallen hat.
Ich verstand die Essenz der Architektur und die Beziehung zwischen ViewModel und View (ViewController) nicht sofort, sondern begann zu tun, was ich verstand. Die Augen haben Angst und die Hände tippen verzweifelt den Code.
Zu meiner Verteidigung möchte ich hinzufügen, dass zu dieser Zeit die Zeit und die Zeit zum Nachdenken und Analysieren der von mir geschaffenen Schöpfung sehr eng waren. Daher habe ich anstelle von Ordnern im ViewModel direkte Links zur entsprechenden Ansicht erstellt. Und bereits in ViewModel selbst habe ich die Präsentation eingerichtet.
In Bezug auf MVP hatte ich die gleiche Idee wie in Bezug auf andere Architekturen, daher war ich fest davon überzeugt, dass es MVVM war, bei dem sich ViewModel als die realistischsten Präsentationen herausstellte.
Ein Beispiel für meine „MVVM“ -Architektur und ja, mir hat die Idee mit RootViewController gefallen, der für die höchste Navigationsebene in der Anwendung verantwortlich ist. Über den Router wird unten geschrieben. import UIKit class RootViewController: UIViewController { var viewModel: RootViewModel? override func viewDidLoad() { super.viewDidLoad() let router = (UIApplication.shared.delegate as? AppDelegate)!.router viewModel = RootViewModel(with: self, router: router) viewModel?.setup() } } import UIKit protocol ViewModelProtocol: class { func setup() func backAction() } class RootViewModel: NSObject, ViewModelProtocol { unowned var router : RootRouter unowned var view: RootViewController init(with view: RootViewController, router: RootRouter) { self.view = view self.router = router }
Dies hatte keinen besonderen Einfluss auf die Qualität des Projekts, da die Reihenfolge und ein einziger Ansatz eingehalten wurden. Aber die Erfahrung war von unschätzbarem Wert. Nach den von mir entworfenen Fahrrädern begann ich endlich, mich nach allgemein anerkannter Architektur zu richten. Es sei denn, Moderatoren wurden als Moderatoren bezeichnet, was einen Drittentwickler verwirren könnte.
Ich entschied, dass es sich in Zukunft lohnt, kleine Testprojekte durchzuführen, um die Essenz eines bestimmten Ansatzes im Design genauer zu untersuchen. Sozusagen erst in der Praxis fühlen und dann in den Kampf einbrechen. Dies ist die Schlussfolgerung, die ich für mich selbst gezogen habe.
Der zweite Versuch mit Navigation oder Router und der Krümmung der Navigation
Im selben Projekt, in dem ich MVVM tapfer und naiv implementiert habe, habe ich beschlossen, einen neuen Ansatz für die Navigation zwischen Bildschirmen auszuprobieren. Wie ich bereits erwähnt habe, habe ich mich immer noch an die Idee gehalten, Bildschirme und die Logik des Übergangs zwischen ihnen zu trennen.
Als ich über MVVM las, interessierte mich ein Muster wie Router. Nachdem ich die Beschreibung erneut überprüft hatte, begann ich, die Lösung in meinem Projekt zu implementieren.
Router Beispiel import UIKit protocol Router: class { func route(to routeID: String, from view: UIViewController, parameters: Any?) func back(from view: UIViewController, parameters: Any?) } extension Router { func back(from view: UIViewController, parameters: Any?) { let navigationController: UINavigationController = checkNavigationController(for: view) navigationController.popViewController(animated: false) } } enum RootRoutes: String { case launch = "Launch" case loginregistartion = "LoginRegistartionRout" case mainmenu = "MainMenu" } class RootRouter: Router { var loginRegistartionRouter: LoginRegistartionRouter? var mainMenuRouter: MainMenuRouter?
Der Mangel an Erfahrung bei der Umsetzung eines solchen Musters hat sich bemerkbar gemacht. Alles schien ordentlich und klar zu sein, der Router erstellte eine neue UIViewController-Klasse, erstellte ein ViewModel dafür und führte die Logik zum Umschalten auf diesen Bildschirm aus. Dennoch machten sich viele Mängel bemerkbar.
Schwierigkeiten traten auf, als es notwendig war, eine Anwendung mit einem bestimmten Bildschirm nach einer Push-Benachrichtigung zu öffnen. Infolgedessen haben wir an einigen Stellen eine verwirrende Logik für die Auswahl des richtigen Bildschirms und weitere Schwierigkeiten bei der Unterstützung eines solchen Ansatzes.
Ich habe die Idee der Implementierung von Router nicht aufgegeben, sondern bin in diese Richtung weitergegangen und habe immer mehr Erfahrung gesammelt. Geben Sie nach dem ersten fehlgeschlagenen Versuch nichts auf.
Hartnäckiger Manager
Eine weitere interessante Managerklasse in meiner Praxis. Aber dieser ist relativ jung. Trotzdem besteht der Entwicklungsprozess aus Versuch und Irrtum, und da wir alle, oder die meisten von uns, ständig im Entwicklungsprozess sind, treten immer Fehler auf.
Das Wesentliche des Problems ist, dass die Anwendung über Dienste verfügt, die ständig hängen bleiben müssen und gleichzeitig an vielen Stellen verfügbar sein sollten.
Beispiel: Ermitteln des Bluetooth-Status. In meiner Anwendung muss ich in mehreren Diensten verstehen, ob Bluetooth aktiviert oder deaktiviert ist, und Statusaktualisierungen abonnieren. Da es mehrere solcher Stellen gibt: einige Bildschirme, mehrere zusätzliche Geschäftslogikmanager usw., muss jeder von ihnen den Delegierten CBPeripheralManager (oder CBCentralManager) abonnieren.
Die Lösung scheint offensichtlich zu sein. Wir erstellen eine separate Klasse, die den Status von Bluetooth überwacht und alle, die ihn benötigen, über das Observer-Muster benachrichtigt. Aber dann stellt sich die Frage, wer diesen Service dauerhaft speichern wird. Das erste, was mir in diesem Moment einfällt, ist, es zu einem Singleton zu machen! Alles scheint in Ordnung zu sein!
Aber hier kommt der Moment, in dem sich mehr als ein solcher Dienst in meiner Bewerbung angesammelt hat. Ich möchte auch keine 100500 Singletones im Projekt machen.
Und dann ging ein weiteres Licht über meinem bereits hellen kleinen Kopf an. Erstellen Sie einen Singleton, der alle diese Dienste speichert und in der gesamten Anwendung Zugriff darauf bietet. Und so wurde der "ständige Manager" geboren. Mit dem Namen habe ich lange nicht nachgedacht und ihn, wie jeder schon erraten konnte, PersistentManager genannt.
Wie Sie sehen können, habe ich auch einen sehr originellen Ansatz für die Benennung von Klassen. Ich denke, ich muss eine Modeerscheinung über den Namen der Klassen in meinen Entwicklungsplan aufnehmen.
Das Hauptproblem bei dieser Implementierung ist der Singleton, der überall im Projekt verfügbar ist. Dies führt dazu, dass Manager, die einen der permanenten Dienste nutzen, innerhalb ihrer Methoden darauf zugreifen, was nicht offensichtlich ist. Zum ersten Mal bin ich darauf gestoßen, als ich ein großes komplexes Feature in einem separaten Demo-Projekt erstellt und einen Teil der Geschäftslogik aus dem Hauptprojekt übertragen habe. Dann erhielt ich Nachrichten mit Fehlern über fehlende Dienste.
Die Schlussfolgerung, die ich danach gezogen habe, ist, dass Sie Ihre Klassen so gestalten müssen, dass es keine versteckten Abhängigkeiten gibt. Die erforderlichen Dienste müssen beim Initialisieren der Klasse als Parameter übergeben werden, dürfen jedoch keinen Singleton verwenden, auf den von überall aus zugegriffen werden kann. Und noch schöner ist es, Protokolle zu verwenden.
Dies stellte sich als eine weitere Bestätigung für das Fehlen eines Singleton-Musters heraus.
Zusammenfassung
Und das, ich stehe nicht still, sondern gehe voran und beherrsche neue Ansätze in der Programmierung. Die Hauptsache ist, sich zu bewegen, zu suchen und zu experimentieren. Fehler werden immer sein, es gibt kein Entrinnen davon. Aber nur weil man seine Fehler erkennt, kann man sich qualitativ entwickeln.

In den meisten Fällen sind die Probleme Superklassen, die viel bewirken, oder die falschen Abhängigkeiten zwischen Klassen. Was darauf hindeutet, dass es notwendig ist, die Logik kompetenter zu zerlegen.