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).
