My experience of mistakes
List of errors
- Almighty MCManager class
- We invent our navigation between the screens.
- Inheritance does not happen much
- The architecture of our own production or continue to create bicycles
- MVVM with soul MVP
- Second attempt with navigation or router and navigation curvature
- Persistent manager
Many, including me, write how to act correctly in a given situation, how to write code correctly, how to correctly apply architectural solutions, etc. But I would like to share my experience with what was done wrong and the conclusions that I made based on my mistakes. Most likely it will be common mistakes of all who follow the path of the developer, and maybe something will be a little different. I just want to share my experience and read the comments of other guys.
Almighty MCManager class
After the first year of working in IT, and more specifically iOS development, I decided that I was already quite an architect and was quite ready to create. Even then, I intuitively understood that it was necessary to separate business logic from the presentation layer. But the quality of my idea of ​​how to do this was far from reality.
I moved to a new job, where I was assigned to independently develop a new feature for an existing project. It was an analogue of video recording in Instagram, where the recording is made while the user holds his finger on the button, and then several video fragments were joined together. Initially, it was decided to make this feature as a separate project, but rather as a sample. In this, as I understand it, begins the source of my architectural problems, which stretched for more than a year.
In the future, this sample has grown into a full-fledged application for recording and editing video. It's funny that initially the sample had a name, from the abbreviation of which the prefix MC was assembled. Although the project was soon renamed, the prefix, as required by the name convention in Objective-C, remained the MC. Thus was born the almighty class MCManager.
')

Since it was a sample and at first the functionality was simple, I decided that one class manager would be enough. The functional, as I mentioned earlier, included recording a video clip with the start / stop options and further combining these fragments into a whole video. And at this very moment I can name my first mistake - the name of the class MCManager. MCManager, Karl! What should a class name tell other developers about its purpose, its capabilities, how to use it? That's right, absolutely nothing! And this is in the application, whose name does not even contain the letters M, and his mother, C. Although, this is not my main mistake, since the class with the partisan name did everything, everything from the word was absolutely everything, which was the key error.
Video recording is one small service, managing the storage of video files in the file system is the second and additionally a service for combining several videos into one. It was decided to combine the work of these three independent services in one manager. The idea was noble, using the facade pattern, create a simple interface for business logic and hide all unnecessary details on the interaction of various components. At the initial stages, even the name of such a facade class did not arouse any suspicions, especially in the sample.
But the customer liked the demo and soon the sample turned into a full-fledged application. One can justify that there was not enough time to refactor, that the customer did not want to redo the working code, but frankly, I myself at that moment thought that I had laid the excellent architecture. In truth, the idea of ​​separating business logic and presentation was a success. The architecture was a single MCManager singleton class, which was a facade for a couple of dozen services and other managers. Yes, it was also a singleton, which was available from all corners of the application.
You can already understand the scale of the disaster. A class with a few thousand lines of code that is difficult to read and difficult to maintain. I am already silent about the possibility of selecting individual features in order to transfer them to another application, which is quite common in mobile development.
Conclusions that I made for myself after a while - not to create universal classes with obscure names. I realized that the logic needs to be broken apart and not create a universal interface for everything. In essence, this was an example of what would happen if one did not adhere to one of the SOLID principles, the Interface Segregation Principle.
We invent our navigation between the screens.
The separation of logic and interface is not the only question that worried me on the aforementioned project. I wouldn’t say that at that moment I was going to separate the code of the screens and the navigation code, but it turned out that I had invented my bicycle for navigation.

The sample had only three screens: a menu with a table of recorded videos, a recording screen and a post-processing screen. In order not to care about the fact that the navigation stack contains duplicate ViewController, I decided not to use UINavigationController. I added RootViewcontroller, attentive readers already guessed that it was MCRootViewController, which was set as the main one in the project settings. At the same time, the controller's root was not one of the application screens, it simply presented the desired UIViewController. As if that was not enough, the root controller was also a delegate of all the represented controllers. As a result, at each time point in the hierarchy there were only two vc, and all navigation was implemented through the delegate pattern.
What it looked like: each screen had its own delegate protocol, where navigation methods were designated, and the root controller implemented these methods and changed screens. RootViewController dissmys the current controller, created a new one and presented it, while it was possible to transfer information from one screen to another. Fortunately, business logic was in the coolest singleton class, so none of the screens did not store anything and could be safely destroyed. Again, the good intention was implemented, although the implementation limped on both legs, and sometimes stumbled.
As you can guess, if you need to go from the video recording screen back to the main menu, the method was called:
- (void)cancel;
or something like this, and the root controller already does all the dirty work.
In the end, MCRootViewController, became an analogue of MCManager, but in the navigation between the screens, as with the growth of the application and the addition of new functionality, new screens were added.
The bicycle plant worked relentlessly, and I continued to ignore articles about mobile application architectures. But I never refused the idea of ​​separating navigation from the screens.
The advantage was that the screens were independent and could be reused, but this is not accurate. But the disadvantages include the difficulty in maintaining such classes. The problem with the lack of a stack of screens when you need to go back, scrolling through the previously selected screens. The complex logic of the transition between the screens, the root controller affected part of the business logic to correctly display the new screen.
In general, it is not necessary to implement all the navigation in the application in this way, since my MCRootViewController violated the principle of Open-Closed Principle (the principle of openness-closeness). It is almost impossible to expand it, and all changes must be constantly made to the class itself.
I began to read more about the navigation between the screens in the mobile application, I got acquainted with such approaches as Router and Coordinator. I will write about Router a little later, the blessing is what to share.
Inheritance does not happen much
I would also like to share not only my own pearls, but also other people's funny approaches and solutions that I had to face. In the same place where I created my masterpieces, I was assigned a simple task. The task was to add a screen from another project to my project. As PM and I determined, after a shallow analysis and brief reflections, this should have taken two to three hours and no more, because what is there, you need to add a ready-made screen class to your application. And the truth is, everything has already been done for us, you need to do ctrl + c and ctrl + v. That's just a little nuance, the developer who wrote this application, really loved inheritance.

I quickly found the ViewController I needed, I was lucky that there was no separation of logic and representation. It was a good old approach when the controller contained all the necessary code. I copied it into my project and started to figure out how to make it work. And the first thing I discovered is that the controller I need inherits another controller. The usual thing is quite an expected event. Since I didn’t have much time, I just found the class I needed and dragged it into my project. Well, it should earn now, I thought, and I have never been so wrong!
Not only did the class I needed have a lot of variables of custom classes that also needed to be copied to my project, so each of them inherited something. In turn, the base classes also either inherited, or contained fields with custom types, which, as many have already guessed, inherited something and, to my misfortune, were not NSObject, UIViewController or UIView. Thus, a good third of the unnecessary project migrated to my project.
Since the time expected for this task was not long, I didn’t see any other way out as simply adding the necessary classes that xCode required simply for a painless launch of my project. As a result, two or three hours were a little delayed, because in the end I had to delve into the whole web of inheritance hierarchies, like a true sculptor, cutting off the excess.
In the end, I came to the conclusion that everything good should be in moderation, even such a "wonderful" thing as inheritance. Then I began to understand the disadvantages of inheritance. I made a conclusion for myself, if I want to do reusable modules, I should make them more independent.
The architecture of our own production or continue to create bicycles
Turning to a new job and starting a new project, I took into account all the existing experience in architecture design and continued to create. Naturally, I continued to ignore the already invented architecture, but at the same time I strongly adhered to the principle of "divide and conquer."
It was not long before Swift appeared, so I delved into the possibilities of Objective-c. I decided to make Dependency injection using the features of the language. I was inspired at this by the proppery extension logo, I can’t even remember its name.
The essence is as follows: in the BaseViewController base class, I added the BaseViewModel class field. Accordingly, for each screen I created my own controller, which inherited the basic ones, and added a protocol for the controller to interact with the viewModel. Then came the magic. I redefined the viewModel and added support for the desired protocol. In turn, I created for the screen a new ViewModel class that implemented this protocol. As a result, in the BaseViewController method in the viewDidLoad method I checked the type of the model's protocol, checked the list of all heirs from the BaseViewModel, found the class I needed and created the viewModel of the type I needed.
Base ViewController Example #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
Sample Basic 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
Helper classes #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; }
Login screen example #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
So I simply did a lazy initialization of the viewModel and a weakly connected view with the models by means of protocols. With all this, at that moment I did not know anything about the MVP architecture, although something like this appeared to me.
Navigation between screens remained at the discretion of the “viewModel”, since I added a weak link to the controller.
Remembering this implementation now, I can not say for sure that everything was bad. The idea of ​​separating the layers was a success; the moment of creating and assigning models to the controller was simplified.
But for myself, I decided to study more ready-made approaches and architectures, because during the development of an application with my own architecture, I had to deal with many nuances. For example, reuse of screens and models, inheritance, complex transitions between screens. At that moment it seemed to me that the viewModel was also part of the business of logic, although now I understand that this is still a presentation layer. I got a great experience during this experiment.
MVVM with soul MVP
Having already gained experience, I decided to choose for myself a certain architecture and follow it, instead of inventing bicycles. I began to read in more detail about the architectures, to study in detail the popular ones at that moment and stopped at MVVM. Frankly, I did not immediately understand its essence, but I chose it because I liked the name.
I did not immediately understand the essence of the architecture and the connection between ViewModel and View (ViewController), but I started to do it as I understood. The eyes are afraid, and the hands frantically dial the code.
In my excuse, I will add that at that time the deadlines and time for thinking were very tight and analysis of the creation I had created was not enough. Therefore, instead of binding, I made direct links in the ViewModel to the corresponding View. And already in the ViewModel itself did the setting of the view.
I had the same idea about MVP as about other architectures, so I firmly believed that it was MVVM, in which the ViewModel turned out to be the most real presenters.
An example of my “MVVM” architecture and yes, I liked the idea with the RootViewController, which is responsible for the highest level of navigation in the application. About the router is written below. 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 }
This was not particularly reflected in the quality of the project, since order and a uniform approach were observed. But the experience was invaluable. After the bikes I created, I finally began to make it according to the generally accepted architecture. Is that the presenters were not called presenters, which could confuse a third-party developer.
I decided that in the future it would be worthwhile to do small test projects, to delve more into the essence of this or that approach in the design. So to say, first feel in practice, and then rush into battle. This is the conclusion I made for myself.
Second attempt with navigation or router and navigation curvature
On the same project, where I valiantly and naively implemented MVVM, I decided to try a new approach in navigation between the screens. As I mentioned earlier, I still adhered to the idea of ​​separating screens and the logic of the transition between them.
Reading about MVVM, I was interested in such a pattern as Router. Again, having read the description, I began to implement the solution in my project.
Router example 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?
Lack of experience in the implementation of such a pattern manifested itself. It seems that everything was neat and clear, the router created a new UIViewController class, created a ViewModel for it and executed the logic of switching to this screen. But still, a lot of shortcomings made themselves felt.
Difficulties began to arise when it was necessary to open an application with a certain screen after a push notification. As a result, in some places we got confused logic on the choice of the desired screen and further difficulty in supporting such an approach.
I did not abandon the idea of ​​implementing Router, but continued in this direction, gaining more and more experience. Do not give up something after the first failed attempt.
Persistent manager
Another interesting class manager in my practice. But this one is relatively young. All the same, the development process consists of trial and error, and since we all, well, or most of us, are constantly in the process of development, errors always appear.
The essence of the problem is that there are services in the application that should hang constantly and at the same time should be available in many places.
Example: determining the status of Bluetooth. In my application in several services you need to understand whether bluetooth is on or off and subscribe to status updates. Since there are several such places: a couple of screens, several additional business logic managers, and so on, there is a need for each of them to subscribe to the delegate CBPeripheralManager (or CBCentralManager).
The solution seems obvious, we create a separate class that monitors the status of the bluetooth and through the Observer pattern to notify everyone who needs it. But then the question arises, who will keep this service permanently? The first thing that comes to mind at this moment is to make it a singleton! It seems everything is OK!
But then there is a moment that more than one such service has accumulated in my application. I don't want to do 100,500 singletons in the project either.
And here over my and so light head the next bulb lit up. Make one singleton that will store all such services and provide access to them throughout the application. Thus was born the "permanent manager." With the title, I did not think for a long time and called it, as everyone could guess, PersistentManager.
As you can see, I also have a very original approach to class naming. I think you need in my development plan to add a fad about the name of classes.
The key issue in this implementation is Singleton, which is available anywhere in the project. And this leads to the fact that managers who use one of the permanent services access it within their methods, which is not obvious. I first encountered this when I made a big complex feature in a separate demo project and transferred part of the business logic from the main project. Then I started receiving messages with errors about missing services.
The conclusion I made after this is that you need to design your classes in such a way that there are no hidden dependencies. Necessary services should be passed as parameters when initializing a class, but not using a singleton, which can be accessed from any place. And even more beautiful is to do it using protocols.
This turned out to be another confirmation of the lack of the singleton pattern.
Total
Moreover, I am not standing still, but moving forward, learning new approaches in programming. The main thing to move, search and experiment. Errors will always be, this will not go anywhere. That's just the same due to the awareness of their mistakes, you can develop qualitatively.

In most cases, problems are superclasses that do a lot, or wrong dependencies between classes. What makes you think that you need to competently decompose the logic.