MVC is not sufficient
Every day iOS apps become more cumbersome and bulky, which makes MVC approach not sufficient.
We are seeing more and more classes for different purposes: app logic extracts into services, models extends with decorators, view separates into partials and so on. And – what is more important – there are now a lot of dependencies that need to be managed somehow.
Singleton is often used to resolve this problem; this is a kind of global variable that everyone can access. How many times have you seen such a code:
[[RequestManager sharedInstance] loadResourcesAtPath:@"http://example.com/resources" withDelegate:self]; // or [[DatabaseManager sharedManager] saveResource:resource];
A lot of projects use this approach, but it entails a few flaws:
- it’s hard to mock or stub Singletons while testing classes that use it
- Singleton is a global variable by the nature
- from the SRP point of view, object should not control his Singleton behaviour
The first problem is easy to solve – use object property:
@interface ViewController : UIViewController @property (nonatomic, strong) RequestManager *requestManager; @end
But this approach entails another issue – someone should ‘fill’ this property.
Dependency Injection
These issues are not unique for Objective-C. If we take a look at more ‘industrial’ languages, e.g. Java or C++, we can find a solution. Widely used approach in Java – Dependency Injection(DI).
DI allows injecting requestManager as a singleton in the application, but uses mock object while testing. No one is aware of singleton however – neither RequestManager, nor ViewController – because all this stuff is managed by DI framework.
You can find a lot of Objective-C implementations of the Dependency Injection pattern on GitHub, but they have some disadvantages:
- description of dependencies via macros or string constants
- injection occurs only if object is created in a special way (this would not work at least with UIViewController from Storyboard and with UIView from Nib)
- injected class should implement some protocol (this would not work with third party libraries)
- initialization can’t be placed into a separate module
- uses XML to describe dependencies
BloodMagic
So, let’s look into another framework (also with its set of disadvantages) – BloodMagic.
BloodMagic provides some sort of custom attributes. It’s designed to be extensible, so more features are coming soon. Currently, only one attribute is implemented – Lazy.
This attribute allows to initialize objects on demand with a minimum of boilerplate code. Therefore, instead of the following sheets:
@interface ViewController : UIViewController @property (nonatomic, strong) ProgressViewService *progressViewService; @property (nonatomic, strong) ResourceLoader *resourceLoader; @end @implementation ViewController - (void)loadResources { [self.progressViewService showProgressInView:self.view]; self.resourceLoader.delegate = self; [self.resourceLoader loadResources]; } - (ProgressViewService *)progressViewService { if (_progressViewService == nil) { _progressViewService = [ProgressViewService new]; } return _progressViewService; } - (ResourceLoader *)resourceLoader { if (_resourceLoader == nil) { _resourceLoader = [ResourceLoader new]; } return _resourceLoader; } @end
we can simply write:
@interface ViewController : UIViewController @property (nonatomic, strong) ProgressViewService *progressViewService; @property (nonatomic, strong) ResourceLoader *resourceLoader; @end @implementation ViewController @dynamic progressViewService; @dynamic resourceLoader; - (void)loadResources { [self.progressViewService showProgressInView:self.view]; self.resourceLoader.delegate = self; [self.resourceLoader loadResources]; } @end
And that’s it. Both @dynamic properties are created by a call to self.progressViewService and self.resourceLoader. They are released in the same way as a standard properties – after ViewController is deallocated.
BloodMagic and Dependency Injection
+new method is used for object creation here by default. But it also has a capability of writing custom initializers which is a key feature of BMLazy as a DI framework.
Creating custom initializer is a bit cumbersome, but works well:
BMInitializer *initializer = [BMInitializer lazyInitializer]; initializer.propertyClass = [ProgressViewService class]; initializer.initializer = ^id (id sender){ return [[ProgressViewService alloc] initWithViewController:sender]; }; [initializer registerInitializer];
propertyClass – initializer registers for property of this class.
initializer – block, which is called to initialize property. If this block is nil, or initializer is not found for concrete property, then object is created via +new class method.
sender – container class instance.
Also, initializer has a containerClass property, which allows us to describe creation of the same property based on class container. For example:
BMInitializer *usersLoaderInitializer = [BMInitializer lazyInitializer]; usersLoaderInitializer.propertyClass = [ResourceLoader class]; usersLoaderInitializer.containerClass = [UsersViewController class]; usersLoaderInitializer.initializer = ^id (id sender){ return [ResourceLoader usersLoader]; }; [usersLoaderInitializer registerInitializer]; BMInitializer *projectsLoaderInitializer = [BMInitializer lazyInitializer]; projectsLoaderInitializer.propertyClass = [ResourceLoader class]; projectsLoaderInitializer.containerClass = [ProjectsViewController class]; projectsLoaderInitializer.initializer = ^id (id sender){ return [ResourceLoader projectsLoader]; }; [projectsLoaderInitializer registerInitializer];
Thus, for UsersViewController and ProjectsViewController different objects are created. By default containerClass equals to NSObject class.
Initializers help to get rid of various shared* methods and hardcoding, described in the beginning:
BMInitializer *initializer = [BMInitializer lazyInitializer]; initializer.propertyClass = [RequestManager class]; initializer.initializer = ^id (id sender){ static id singleInstance = nil; static dispatch_once_t once; dispatch_once(&once, ^{ singleInstance = [RequestManager new]; }); return singleInstance; }; [initializer registerInitializer];
Organize initializers
We may have a lot of initializers, so it makes sense to move them into a separate place.
Nice solution is to store them in different files and use attributes of a compiler (it’s a normal practice). BloodMagic has also a simple macros that hides this attribute – lazy_initializer. We should simply create a source file without header and add it to the compilation phase.
Here is an example:
// SomeInitializer.m #import <BloodMagic/Lazy.h> #import "ResourceLoader.h" #import "UsersViewController.h" #import "ProjectsViewController.h" lazy_initializer ResourseLoaderInitializers() { BMInitializer *usersLoaderInitializer = [BMInitializer lazyInitializer]; usersLoaderInitializer.propertyClass = [ResourceLoader class]; usersLoaderInitializer.containerClass = [UsersViewController class]; usersLoaderInitializer.initializer = ^id (id sender){ return [ResourceLoader usersLoader]; }; [usersLoaderInitializer registerInitializer]; BMInitializer *projectsLoaderInitializer = [BMInitializer lazyInitializer]; projectsLoaderInitializer.propertyClass = [ResourceLoader class]; projectsLoaderInitializer.containerClass = [ProjectsViewController class]; projectsLoaderInitializer.initializer = ^id (id sender){ return [ResourceLoader projectsLoader]; }; [projectsLoaderInitializer registerInitializer]; }
lazy_initializer will be replaced with __attribute__((constructor)) static void. Attribute constructor means that this method will be called before main function (here are details: GCC. Function Attributes).