Mon expérience des erreurs
Liste d'erreurs
- Classe omnipotente MCManager
- Inventer notre navigation entre les écrans
- Il n'y a jamais beaucoup d'héritage
- Architecture de notre propre production ou continuer à créer des vélos
- MVVM avec soul MVP
- La deuxième tentative avec la navigation ou le routeur et la courbure de la navigation
- Gestionnaire persistant
Beaucoup de gens, y compris moi-même, écrivent comment faire la bonne chose dans une situation donnée, comment écrire du code correctement, comment appliquer des solutions architecturales, etc. Mais je voudrais partager mon expérience de la façon dont cela a été mal fait et les conclusions que j'ai fait sur la base de ses erreurs. Il s'agit très probablement d'erreurs courantes de tous ceux qui suivent le chemin du développeur, ou peut-être que quelque chose sera nouveau. Je veux juste partager mon expérience et lire les commentaires des autres gars.
Classe omnipotente MCManager
Après la première année de travail dans l'informatique, et plus particulièrement le développement iOS, j'ai décidé que j'étais déjà suffisamment architecte et tout à fait prêt à créer. Même alors, j'ai intuitivement compris qu'il était nécessaire de séparer la logique métier de la couche de présentation. Mais la qualité de mon idée de la façon de procéder était loin de la réalité.
J'ai déménagé dans un nouveau lieu de travail, où j'ai été affecté à développer de manière indépendante une nouvelle fonctionnalité pour un projet existant. C'était un analogue de l'enregistrement de vidéos sur Instagram, où l'enregistrement est effectué pendant que l'utilisateur tient son doigt sur le bouton, puis plusieurs fragments de la vidéo sont connectés ensemble. Dans un premier temps, il a été décidé de faire de cette fonctionnalité un projet distinct, ou plutôt sous la forme d'un échantillon. Cela, si je comprends bien, commence la source de mes problèmes architecturaux, qui ont duré plus d'un an.
À l'avenir, cet échantillon est devenu une application à part entière pour l'enregistrement et l'édition de vidéos. C'est drôle qu'initialement l'échantillon ait un nom, à partir de l'abréviation dont le préfixe MC a été collecté. Bien que le projet ait été rapidement renommé, mais le préfixe requis par la convention de nom dans Objective-C, MC est resté. La toute puissante classe MCManager était donc née.

Comme il s'agissait d'un exemple et que la fonctionnalité était simple au départ, j'ai décidé qu'une seule classe de gestionnaire serait suffisante. La fonctionnalité, comme je l'ai mentionné plus tôt, comprenait l'enregistrement d'un fragment vidéo avec des options de démarrage / arrêt et la combinaison de ces fragments dans une vidéo entière. Et à ce moment précis, je peux nommer ma première erreur - le nom de la classe MCManager. MCManager, Karl! Que devrait dire un nom de classe aux autres développeurs sur son objectif, ses capacités et comment l'utiliser? Bon, absolument rien! Et c'est en annexe, dont le nom ne contient même pas les lettres M et, sa mère, C. Bien que ce ne soit pas ma principale erreur, car la classe avec le nom partisan a tout fait, tout du mot est absolument tout, ce qui était l'erreur clé.
L'enregistrement vidéo est un petit service, la gestion du stockage des fichiers vidéo dans le système de fichiers est le deuxième service supplémentaire pour combiner plusieurs vidéos en une seule. Le travail de ces trois services indépendants, il a été décidé de regrouper en un seul manager. L'idée était noble, en utilisant le modèle de façade, pour créer une interface simple pour la logique métier et masquer tous les détails inutiles sur l'interaction des différents composants. Au début, même le nom d'une telle classe de façade n'a soulevé aucun soupçon, en particulier dans l'échantillon.
Mais le client a aimé la démo et bientôt l'échantillon s'est transformé en une application à part entière. Vous pouvez justifier qu'il n'y avait pas assez de temps pour le refactoring, que le client ne voulait pas refaire le code de travail, mais franchement, à ce moment-là, je pensais moi-même avoir posé une excellente architecture. En vérité, l'idée de séparer la logique métier et la présentation a été un succès. L'architecture était une classe de singleton MCManager, qui était la façade de quelques dizaines de services et autres gestionnaires. Oui, c'était aussi un singleton, qui était disponible à tous les coins de l'application.
On peut déjà comprendre l'ampleur de toute la catastrophe. Une classe avec plusieurs milliers de lignes de code difficiles à lire et à maintenir. Je suis déjà silencieux sur la possibilité de mettre en évidence des fonctionnalités individuelles afin de les transférer vers une autre application, ce qui est assez courant dans le développement mobile.
Les conclusions que je me suis faites après un certain temps ne sont pas de créer des classes universelles avec des noms obscurs. J'ai réalisé que la logique doit être brisée en morceaux et ne pas créer une interface universelle pour tout. En fait, c'était un exemple de ce qui se passerait si vous n'adhérez pas à l'un des principes SOLIDES, le principe de séparation d'interface.
Inventer notre navigation entre les écrans
La séparation de la logique et de l'interface n'est pas le seul problème qui m'inquiète pour le projet ci-dessus. Je ne dirai pas qu'à ce moment-là j'allais séparer le code d'écran et le code de navigation, mais il s'est avéré que je suis venu avec mon vélo pour la navigation.

L'échantillon ne comportait que trois écrans: un menu avec un tableau de vidéos enregistrées, un écran d'enregistrement et un écran de post-traitement. Afin de ne pas faire attention à ce que la pile de navigation contienne des ViewControllers en double, j'ai décidé de ne pas utiliser UINavigationController. J'ai ajouté le RootViewcontroller, les lecteurs attentifs ont déjà deviné que c'était le MCRootViewController, qui était défini comme le principal dans les paramètres du projet. Dans le même temps, le contrôleur racine n'était pas l'un des écrans d'application, il présentait simplement l'UIViewController souhaité. Comme si cela ne suffisait pas, le contrôleur racine était également un délégué de tous les contrôleurs représentés. Par conséquent, à chaque instant, il n'y avait que deux vc dans la hiérarchie et toute la navigation était implémentée à l'aide du modèle délégué.
À quoi cela ressemblait: chaque écran avait son propre protocole de délégué, où les méthodes de navigation étaient indiquées, et le contrôleur racine implémentait ces méthodes et changeait les écrans. RootViewController a dissipé le contrôleur de courant, en a créé un nouveau et l'a présenté, alors qu'il était possible de transférer des informations d'un écran à un autre. Heureusement, la logique métier était dans la classe singleton la plus cool, donc aucun des écrans ne stockait rien et pouvait être détruit sans douleur. Encore une fois, une bonne intention a été réalisée, bien que la réalisation ait boité sur les deux jambes, et ait parfois trébuché.
Comme vous pouvez le deviner, si vous devez revenir de l'écran d'enregistrement vidéo au menu principal, la méthode a été appelée:
- (void)cancel;
ou quelque chose comme ça, et le contrôleur racine fait déjà tout le sale boulot.
Au final, MCRootViewController est devenu un analogue de MCManager, mais dans la navigation entre les écrans, comme avec la croissance de l'application et l'ajout de nouvelles fonctionnalités, de nouveaux écrans ont été ajoutés.
L'usine de vélos fonctionnait inexorablement, et j'ai continué à ignorer les articles sur les architectures d'applications mobiles. Mais je n'ai jamais abandonné l'idée de séparer la navigation des écrans.
L'avantage était que les écrans étaient indépendants et pouvaient être réutilisés, mais ce n'est pas exact. Mais les inconvénients incluent la difficulté de maintenir de telles classes. Le problème avec l'absence d'une pile d'écrans lorsque vous devez revenir en faisant défiler les écrans précédemment sélectionnés. La logique complexe de transition entre les écrans, le contrôleur racine a affecté une partie de la logique métier afin d'afficher correctement un nouvel écran.
En général, vous ne devez pas implémenter toute la navigation dans l'application de cette manière, car mon MCRootViewController a violé le principe du principe ouvert-fermé. Il est presque impossible de développer, et tous les changements doivent être constamment apportés à la classe elle-même.
J'ai commencé à en savoir plus sur la navigation entre les écrans dans une application mobile, me suis familiarisé avec des approches telles que routeur et coordinateur. J'écrirai sur Router un peu plus tard, car il y a quelque chose à partager.
Il n'y a jamais beaucoup d'héritage
Je veux aussi partager non seulement mes perles, mais aussi les approches et solutions drôles des autres avec lesquelles j'ai dû faire face. Au même endroit où j'ai créé mes chefs-d'œuvre, ils m'ont confié une tâche simple. La tâche consistait à ajouter un écran d'un autre projet à mon projet. Comme nous l'avons déterminé avec PM, après une analyse peu approfondie et un peu de réflexion, cela aurait dû prendre deux ou trois heures et pas plus, car ce qui ne va pas, il vous suffit d'ajouter une classe d'écran prête à l'emploi à votre application. En effet, tout a déjà été fait pour nous, il faut faire ctrl + c et ctrl + v. Voici juste une petite nuance, le développeur qui a écrit cette application a vraiment adoré l'héritage.

J'ai rapidement trouvé le ViewController dont j'avais besoin, j'ai eu la chance qu'il n'y ait pas de séparation entre logique et présentation. C'était une bonne vieille approche, lorsque le contrôleur contenait tout le code nécessaire. Je l'ai copié dans mon projet et j'ai commencé à comprendre comment le faire fonctionner. Et la première chose que j'ai découverte est que le contrôleur dont j'ai besoin hérite d'un autre contrôleur. Une chose courante, un événement assez attendu. Comme je n'avais pas beaucoup de temps, je viens de trouver la classe dont j'avais besoin et de la faire glisser vers mon projet. Eh bien maintenant ça devrait marcher, pensais-je, et je n'avais jamais eu autant tort!
Non seulement la classe dont j'avais besoin avait de nombreuses variables de classes personnalisées qui devaient également être copiées dans mon projet, donc chacune d'elles a hérité de quelque chose. À leur tour, les classes de base étaient héritées ou contenaient des champs avec des types personnalisés qui, comme beaucoup auraient pu le deviner, héritaient de quelque chose, et ce n'était malheureusement pas NSObject, UIViewController ou UIView. Ainsi, un bon tiers du projet inutile a migré vers moi dans le projet.
Comme il n'y avait pas beaucoup de temps prévu pour terminer cette tâche, je n'ai pas vu d'autre moyen de simplement ajouter les classes nécessaires dont xCode avait besoin simplement pour lancer mon projet sans douleur. En conséquence, deux ou trois heures ont traîné un peu, car au final j'ai dû plonger dans tout le tissu de la hiérarchie de l'héritage, comme un vrai sculpteur, coupant l'excès.
En conséquence, je suis arrivé à la conclusion que toutes les bonnes choses devraient être avec modération, même une chose aussi «merveilleuse» que l'héritage. Puis j'ai commencé à comprendre les inconvénients de l'héritage. J'ai conclu pour moi-même, si je veux faire des modules réutilisables, je devrais les rendre plus indépendants.
Architecture de notre propre production ou continuer à créer des vélos
En déménageant dans un nouveau lieu de travail et en commençant un nouveau projet, j'ai pris en compte toute l'expérience disponible dans la conception d'architecture et j'ai continué à créer. Naturellement, j'ai continué à ignorer les architectures déjà inventées, mais en même temps j'ai toujours adhéré au principe de «diviser pour régner».
Il n'y avait pas longtemps avant l'avènement de Swift, j'ai donc exploré les possibilités d'Objective-c. J'ai décidé de faire l'injection de dépendances en utilisant les fonctionnalités du langage. J'ai été inspiré par l'outil d'extension lib; je ne me souviens même pas de son nom.
L'essentiel est: dans la classe de base BaseViewController, j'ai ajouté un champ de classe BaseViewModel. En conséquence, pour chaque écran, j'ai créé mon propre contrôleur, qui a hérité des basiques, et j'ai ajouté un protocole pour que le contrôleur interagisse avec le viewModel. Puis vint la magie. J'ai redéfini les propriétés de viewModel et ajouté la prise en charge du protocole souhaité. À mon tour, j'ai créé une nouvelle classe ViewModel pour un écran particulier qui a implémenté ce protocole. Par conséquent, dans le BaseViewController de la méthode viewDidLoad, j'ai vérifié le type du protocole du modèle, vérifié la liste de tous les descendants de BaseViewModel, trouvé la classe dont j'avais besoin et créé le viewModel du type dont j'avais besoin.
Exemple de base de ViewController #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
Exemple de base de ViewModel #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
Classes d'assistance #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; }
Exemple d'écran de connexion #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
D'une manière si simple, j'ai fait l'initialisation paresseuse de viewModel et mal connecté la vue aux modèles via des protocoles. Avec tout cela, à ce moment-là, je ne savais toujours rien de l'architecture MVP, bien que quelque chose de similaire m'ait menacé.
La navigation entre les écrans a été laissée à la discrétion de «viewModel», car j'ai ajouté un maillon faible au contrôleur.
En me souvenant de cette implémentation maintenant, je ne peux pas dire avec certitude que tout était mauvais. L'idée de séparer les couches a été un succès, le moment de créer et d'assigner des modèles au contrôleur a été simplifié.
Mais pour moi, j'ai décidé d'en savoir plus sur les approches et architectures prêtes à l'emploi, car lors du développement d'une application avec ma propre architecture, j'ai dû faire face à de nombreuses nuances. Par exemple, réutilisation d'écrans et de modèles, héritage, transitions complexes entre écrans. À ce moment-là, il me semblait que viewModel faisait partie de la logique métier, même si maintenant je comprends qu'il s'agit toujours d'une couche de présentation. J'ai acquis une grande expérience au cours de cette expérience.
MVVM avec soul MVP
Ayant déjà acquis de l'expérience, j'ai décidé de choisir une architecture spécifique pour moi et de la suivre, au lieu d'inventer des vélos. J'ai commencé à en savoir plus sur les architectures, à étudier en détail ce qui était populaire à l'époque et à m'installer sur MVVM. Franchement, je n'ai pas tout de suite compris son essence, mais je l'ai choisi parce que j'aimais son nom.
Je n'ai pas immédiatement compris l'essence de l'architecture et la relation entre ViewModel et View (ViewController), mais j'ai commencé à faire ce que j'avais compris. Les yeux ont peur et les mains tapent frénétiquement le code.
Pour ma défense, j'ajouterai qu'à cette époque, le temps et le temps pour réfléchir et analyser la création que j'avais créée étaient très serrés. Par conséquent, au lieu de classeurs, j'ai créé des liens directs dans le ViewModel vers la vue correspondante. Et déjà dans ViewModel lui-même, j'ai fait la configuration de la présentation.
À propos de MVP, j'avais la même idée que sur les autres architectures, donc je croyais fermement que c'était MVVM, dans lequel ViewModel s'est avéré être le plus réel des présentateurs.
Un exemple de mon architecture «MVVM» et oui, j'ai aimé l'idée avec RootViewController, qui est responsable du plus haut niveau de navigation dans l'application. À propos du routeur est écrit ci-dessous. 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 }
Cela n'a pas particulièrement affecté la qualité du projet, car l'ordre et une approche unique ont été respectés. Mais l'expérience a été inestimable. Après les vélos que j'ai créés, j'ai finalement commencé à faire selon l'architecture généralement acceptée. À moins que les présentateurs soient appelés présentateurs, ce qui pourrait dérouter un développeur tiers.
J'ai décidé qu'à l'avenir, il valait la peine de faire de petits projets de test, pour approfondir l'essence d'une approche particulière de la conception. Pour ainsi dire, sentez-vous d'abord dans la pratique, puis entrez dans la bataille. Telle est la conclusion que je me suis faite.
La deuxième tentative avec la navigation ou le routeur et la courbure de la navigation
Sur le même projet, où j'ai implémenté vaillamment et naïvement MVVM, j'ai décidé d'essayer une nouvelle approche dans la navigation entre les écrans. Comme je l'ai mentionné plus tôt, j'ai toujours adhéré à l'idée de séparer les écrans et à la logique de transition entre eux.
En lisant MVVM, je m'intéressais à un modèle tel que le routeur. Après avoir revu la description, j'ai commencé à implémenter la solution dans mon projet.
Exemple de routeur 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?
Le manque d'expérience dans la mise en œuvre d'un tel modèle s'est fait sentir. Tout semblait être net et clair, le routeur a créé une nouvelle classe UIViewController, créé un ViewModel pour cela et exécuté la logique pour passer à cet écran. Mais encore, de nombreuses lacunes se sont fait sentir.
Des difficultés ont commencé à survenir lorsqu'il a été nécessaire d'ouvrir une application avec un certain écran après notification push. En conséquence, à certains endroits, nous avons eu une logique confuse pour choisir le bon écran et une difficulté supplémentaire à soutenir une telle approche.
Je n'ai pas abandonné l'idée d'implémenter Router, mais j'ai continué dans cette direction, gagnant de plus en plus d'expérience. N'abandonnez pas quelque chose après la première tentative infructueuse.
Gestionnaire persistant
Une autre classe de manager intéressante dans ma pratique. Mais celui-ci est relativement jeune. Tout de même, le processus de développement consiste en essais et erreurs, et comme nous tous, enfin, ou la plupart d'entre nous, sommes constamment dans le processus de développement, des erreurs apparaissent toujours.
L'essence du problème est la suivante: l'application dispose de services qui doivent constamment se bloquer et en même temps être disponibles dans de nombreux endroits.
Exemple: détermination de l'état de Bluetooth. Dans mon application, dans plusieurs services, je dois comprendre si le Bluetooth est activé ou désactivé et m'abonner aux mises à jour de statut. Puisqu'il existe plusieurs emplacements de ce type: quelques écrans, plusieurs gestionnaires de logique métier supplémentaires, etc., il devient nécessaire pour chacun d'entre eux de s'abonner au délégué CBPeripheralManager (ou CBCentralManager).
La solution semble évidente, nous créons une classe distincte qui surveille l'état du bluetooth et avertit tous ceux qui en ont besoin via le modèle Observer. Mais alors la question se pose, qui va stocker en permanence ce service? La première chose qui me vient à l'esprit en ce moment est d'en faire un singleton! Tout semble aller bien!
Mais voici le moment où plus d'un de ces services s'est accumulé dans ma candidature. Je ne veux pas non plus faire 100500 singletones dans le projet.
Et puis une autre lumière s'est allumée au-dessus de ma petite tête déjà brillante. Créez un singleton qui stockera tous ces services et leur donnera accès tout au long de l'application. Et donc le «manager permanent» est né. Avec le nom, je n'ai pas réfléchi longtemps et je l'ai appelé, comme tout le monde pouvait déjà le deviner, PersistentManager.
Comme vous pouvez le voir, j'ai également une approche très originale du nommage des classes. Je pense que je dois ajouter une mode sur le nom des classes dans mon plan de développement.
Le problème clé de cette implémentation est le singleton, qui est disponible n'importe où dans le projet. Et cela conduit au fait que les managers qui utilisent l'un des services permanents y accèdent à l'intérieur de leurs méthodes, ce qui n'est pas évident. Pour la première fois, je suis tombé sur cela lorsque je créais une grande fonctionnalité complexe dans un projet de démonstration séparé et transférais une partie de la logique métier du projet principal. Puis j'ai commencé à recevoir des messages contenant des erreurs sur les services manquants.
La conclusion que j'ai faite après cela est que vous devez concevoir vos classes de manière à ce qu'il n'y ait pas de dépendances cachées. Les services nécessaires doivent être passés en tant que paramètres lors de l'initialisation de la classe, mais pas en utilisant un singleton, accessible depuis n'importe où. Et encore plus magnifiquement, cela vaut la peine d'utiliser des protocoles.
Cela s'est avéré être une autre confirmation de l'absence d'un modèle singleton.
Résumé
Et ça, je ne reste pas immobile, mais j'avance, maitrisant de nouvelles approches en programmation. L'essentiel est de bouger, chercher et expérimenter. Les erreurs seront toujours, il n'y a pas d'échappatoire à cela. Mais juste à cause de la reconnaissance de ses erreurs, on peut se développer qualitativement.

Dans la plupart des cas, les problèmes sont des super classes qui font beaucoup de choses, ou les mauvaises dépendances entre les classes. Ce qui suggère qu'il est nécessaire de décomposer la logique de manière plus compétente.