My experience of mistakes

    My experience of mistakes


    Error list


    1. Omnipotent class MCManager
    2. Inventing our navigation between screens
    3. There is never much inheritance
    4. Architecture of our own production or continue to create bicycles
    5. MVVM with soul MVP
    6. The second attempt with navigation or Router and the curvature of navigation
    7. Persistent manager

    Many people, including myself, write how to do the right thing in a given situation, how to write code correctly, how to apply architectural solutions, etc. But I would like to share my experience of how it was done incorrectly and the conclusions that I made based on his mistakes. Most likely these will be common mistakes of everyone who follows the path of the developer, or maybe something will be new. I just want to share my experience and read the comments of other guys.

    Omnipotent class MCManager


    After the first year of work in IT, and more specifically iOS development, I decided that I was already an architect enough and 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 place of work, where I was assigned to independently develop a new feature for an existing project. It was an analogue to recording videos on Instagram, where recording is performed while the user holds his finger on the button, and then several fragments of the video are connected together. Initially, it was decided to make this feature as a separate project, or rather in the form of a sample. This, as I understand it, begins the source of my architectural problems, which lasted more than a year.

    In the future, this sample grew into a full-fledged application for recording and editing videos. It's funny that initially the sample had a name, from the abbreviation of which the MC prefix was collected. Although the project was soon renamed, but the prefix as required by name convention in Objective-C, MC remained. So the almighty MCManager class was born.



    Since it was a sample and at first the functionality was simple, I decided that one manager class would be enough. Functionality, as I mentioned earlier, included recording a video fragment with start / stop options and further combining these fragments into a whole video. And at that very moment I can name my first mistake - the name of the MCManager class. MCManager, Karl! What should a class name say to other developers about its purpose, its capabilities, and how to use it? Right, absolutely nothing! And this is in the appendix, the name of which 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 is absolutely everything, which was the key mistake.

    Video recording is one small service, managing the storage of video files in the file system is the second and additional service for combining several videos into one. The work of these three independent services, it was decided to combine in one manager. The idea was noble, using the facade pattern, to create a simple interface for business logic and hide all unnecessary details on the interaction of various components. In the initial stages, even the name of such a facade class did not raise any suspicions, especially in the sample.
    But the customer liked the demo and soon the sample turned into a full-fledged application. You can justify that there was not enough time for refactoring, that the customer did not want to redo the working code, but frankly, at that moment I myself thought that I had laid down an excellent architecture. In truth, the idea of ​​separating business logic and presentation was a success. The architecture was one class of singleton MCManager, which was the 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.

    One can already understand the scale of the whole disaster. A class with several thousand lines of code that is hard to read and hard to maintain. I'm already silent about the possibility of highlighting individual features in order to transfer them to another application, which is quite common in mobile development.
    The conclusions that I made for myself after a while are not to create universal classes with obscure names. I realized that logic needs to be broken into pieces and not create a universal interface for everything. In fact, this was an example of what would happen if you do not adhere to one of the SOLID principles, Interface Segregation Principle.

    Inventing our navigation between screens


    Separation of logic and interface is not the only issue that worried me about the above project. I will not say that at that moment I was going to separate the screen code and the navigation code, but it turned out that I came up with my bike 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 that the navigation stack contains duplicate ViewControllers, I decided not to use the UINavigationController. I added the RootViewcontroller, attentive readers have already guessed that it was the MCRootViewController, which was set as the main one in the project settings. At the same time, the root controller was not one of the application screens, it just presented the desired UIViewController. As if this was not enough, so the root controller was also a delegate of all the controllers represented. As a result, at each moment in time there were only two vc in the hierarchy, and all navigation was implemented using the delegate pattern.

    How it looked: each screen had its own delegate protocol, where navigation methods were indicated, and the root controller implemented these methods and changed screens. RootViewController dissmisil 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 stored anything and could be destroyed painlessly. Again, a good intention was realized, although the realization limped on both legs, and sometimes stumbled.

    As you might 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 that, and the root controller is already doing all the dirty work.
    In the end, MCRootViewController, became an analogue of MCManager, but in the navigation between screens, as with the growth of the application and the addition of new functionality, new screens were added.

    The bicycle factory worked inexorably, and I continued to ignore articles on mobile application architectures. But I never gave up the idea of ​​separating navigation from 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 return by scrolling through the previously selected screens. The complex logic of transition between screens, the root controller affected part of the business logic in order to correctly display a new screen.

    In general, you should not implement all the navigation in the application in this way, since my MCRootViewController violated the Open-Closed Principle principle. It is almost impossible to expand, and all changes must be constantly made to the class itself.
    I began to read more about navigation between screens in a mobile application, got acquainted with such approaches as Router and Coordinator. I’ll write about Router a bit later, since there is something to share.

    There is never much inheritance


    I also want to share not only my pearls, but also other people's funny approaches and solutions that I had to deal with. In the same place where I created my masterpieces, they entrusted me with a simple task. The task was to add a screen from another project to my project. As we determined with PM, after a shallow analysis and a little thought, this should have taken two or three hours and no more, because what’s wrong with this, you just need to add a ready-made screen class to your application. Indeed, everything has already been done for us, we need to do ctrl + c and ctrl + v. Here are just a small 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 presentation. It was a good old approach, when the controller contained all the necessary code. I copied it into my project and began to figure out how to make it work. And the first thing I discovered is that the controller I need inherits from another controller. A common thing, quite an expected event. Since I did not have much time, I just found the class I needed and dragged it to my project. Well now it should work, I thought, and I had never been so wrong!

    Not only did the class I needed had many variables of custom classes that also needed to be copied to my project, so each of them inherited something. In turn, the base classes were either inherited or contained fields with custom types that, as many might have guessed, inherited something, and this, unfortunately, was not NSObject, UIViewController or UIView. Thus, a good third of the unnecessary project migrated to me in the project.

    Since there was not much time expected to complete this task, I did not see any other way out of how to simply add the necessary classes that xCode required simply to launch my project painlessly. As a result, two or three hours dragged on a bit, as in the end I had to delve into the whole web of the hierarchy of inheritance, like a true sculptor, cutting off the excess.

    As a result, I came to the conclusion that all good things should be in moderation, even such a “wonderful” thing as inheritance. Then I began to understand the disadvantages of inheritance. I concluded for myself, if I want to make reusable modules, I should make them more independent.

    Architecture of our own production or continue to create bicycles


    Moving to a new place of work and starting a new project, I took into account all the available experience in the design of architecture and continued to create. Naturally, I continued to ignore the already invented architectures, but at the same time I persistently adhered to the principle of “divide and conquer”.

    It was not long before the advent of Swift, so I delved into the possibilities of Objective-c. I decided to do Dependency injection using the language features. I was inspired by the lib expansion tool; I can’t even remember its name.

    The bottom line is: in the BaseViewController base class, I added a 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 properties and added support for the desired protocol. In turn, I created a new ViewModel class for a particular screen that implemented this protocol. As a result, in the BaseViewController in the viewDidLoad method, I checked the type of the protocol of the model, checked the list of all descendants of BaseViewModel, found the class I needed and created the viewModel of the type I needed.

    Basic ViewController Example
    #import 
    // MVC model
    #import "BaseMVCModel.h"
    @class BaseViewController;
    @protocol BaseViewControllerDelegate 
    @required
    - (void)backFromNextViewController:(BaseViewController *)aNextViewController withOptions:(NSDictionary *)anOptionsDictionary;
    @end
    @interface BaseViewController : UIViewController 
    @property (nonatomic, weak) BaseMVCModel *model;
    @property (nonatomic, assign) id 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
    


    Basic ViewModel Example
    #import 
    @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 
    @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 
    NSString * FindPropertyProtocol(NSString *propertyName, Class class);
    NSArray * ClassGetSubclasses(Class parentClass);
    #import "RuntimeHelper.h"
    #import 
    #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 
    @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 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


    In such a straightforward way, I did lazy initialization of viewModel and poorly connected the view with models through protocols. With all this, at that moment I still did not know anything about the MVP architecture, although something similar loomed over me.
    The navigation between the screens was left to the discretion of “viewModel”, since I added a weak link to the controller.

    Remembering this implementation now, I can’t say for sure that everything was bad. The idea of ​​separating layers was a success, the moment of creating and assigning models to the controller was simplified.
    But for myself, I decided to learn more about ready-made approaches and architectures, since 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 viewModel was part of the business logic, although now I understand that it is still a presentation layer. I got great experience during this experiment.

    MVVM with soul MVP


    Having already gained experience, I decided to choose a specific architecture for myself and follow it, instead of inventing bicycles. I began to read more about architectures, to study in detail popular at that time and settled on 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 relationship between ViewModel and View (ViewController), but started to do as I understood. The eyes are afraid, and the hands are frantically typing the code.

    In my defense, I’ll add that at that time the time and time for thinking and analyzing the creation I created were very tight. Therefore, instead of binders, I made direct links in the ViewModel to the corresponding View. And already in ViewModel itself, I did the presentation setup.

    About MVP, I had the same idea as about other architectures, so I firmly believed that it was MVVM, in which ViewModel turned out to be the most real presentations.

    An example of my “MVVM” architecture and yes, I liked the idea with 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
        }
        // MARK: - ViewModelProtocol
        func setup() {
            if AccountManager.shared.isLoggedIn() {
                router.route(to: RootRoutes.launch.rawValue, from: view, parameters: nil)
            } else {
                router.route(to: RootRoutes.loginregistartion.rawValue, from: view, parameters: nil)
            }
        }
        func backAction() {
        }
    }


    This did not particularly affect the quality of the project, since the order and a single approach were respected. But the experience was invaluable. After the bicycles I created, I finally started to do according to generally accepted architecture. Unless presenters were called presenters, which could confuse a third-party developer.

    I decided that in the future it is worth doing small test projects, to delve into the essence of a particular approach in design in more detail. So to speak, first feel in practice, and then break into the battle. Such is the conclusion I made for myself.

    The second attempt with navigation or Router and the curvature of navigation


    On the same project, where I valiantly and naively implemented MVVM, I decided to try a new approach in navigation between screens. As I mentioned earlier, I still adhered to the idea of ​​separating screens and the logic of transition between them.

    Reading about MVVM, I was interested in such a pattern as Router. After reviewing the description again, 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?
        // MARK: Router
        func route(to routeID: String, from view: UIViewController, parameters: Any?) {
            var rootView = view
            if view is EPLaunchViewController {
                rootView = (view.navigationController?.viewControllers.first)!
                view.navigationController?.popViewController(animated: false)
            }
            if routeID == RootRoutes.loginregistartion.rawValue {
                loginRegistartionRouter = LoginRegistartionRouter(with: self)
                loginRegistartionRouter?.route(to: LRRouteID.phoneNumber.rawValue, from: rootView, parameters: nil)
            } else if routeID == RootRoutes.mainmenu.rawValue {
                if mainMenuRouter == nil {
                    mainMenuRouter = MainMenuRouter(with: self)
                }
                mainMenuRouter?.route(to: MMRouteID.start.rawValue, from: rootView, parameters: nil)
            } else if routeID == RootRoutes.launch.rawValue {
                let storyboard = UIStoryboard(name: "RootStoryboard", bundle: nil)
                let launchView = storyboard.instantiateViewController(withIdentifier: "LaunchViewController") as! LaunchViewController
                let navigationController: UINavigationController = checkNavigationController(for: view)
                launchView.viewModel = LaunchViewModel(with: launchView, router: self)
                navigationController.pushViewController(launchView, animated: false)
            }
        }    
    }
    


    The lack of experience in implementing such a pattern has made itself felt. Everything seemed to be neat and clear, the router created a new UIViewController class, created a ViewModel for it and executed the logic for switching to this screen. But still, many shortcomings made themselves felt.
    Difficulties began to arise when it was necessary to open an application with a certain screen after push notification. As a result, in some places we got confusing logic for choosing the right 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 manager class 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 development process, errors always appear.

    The essence of the problem is this, the application has services that must 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 I need to understand whether bluetooth is turned on or off and subscribe to status updates. Since there are several such places: a couple of screens, several additional business logic managers, etc., it becomes necessary for each of them to subscribe to the CBPeripheralManager (or CBCentralManager) delegate.

    The solution seems to be obvious, we create a separate class that monitors the status of bluetooth and notify everyone who needs it through the Observer pattern. But then the question arises, who will permanently store this service? The first thing that comes to mind at this moment is to make it a singleton! Everything seems to be OK!

    But here comes the moment that more than one such service has accumulated in my application. I do not want to make 100500 singletones in the project either.

    And then another light came on above my already bright little head. Make one singleton that will store all such services and provide access to them throughout the application. And so the "permanent manager" was born. With the name, I didn’t think for a long time and called it, as everyone could already guess, PersistentManager.

    As you can see, I also have a very original approach to class naming. I think I need to add a fad about the name of classes in my development plan.

    The key issue in this implementation is the 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 inside their methods, which is not obvious. For the first time, I came across this when I was making a big complex feature in a separate demo project and transferring part of the business logic from the main project. Then I began to receive 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. The necessary services must be passed as parameters when initializing the class, but not using a singleton, which can be accessed from anywhere. And even more beautifully, it is worth doing using protocols.
    This turned out to be another confirmation of the lack of a singleton pattern.

    Total


    And that, I do not stand still, but move forward, mastering new approaches in programming. The main thing is to move, seek and experiment. Mistakes will always be, there is no escape from this. But just because of the recognition of one’s mistakes, one can develop qualitatively.



    In most cases, the problems are super classes that do a lot, or the wrong dependencies between classes. Which suggests that it is necessary to decompose logic more competently.

    Also popular now: