As we are familiar with MVC design pattern and it is one of the standard approach to build iOS Applications. MVVM is very similar to MVC. It formalizes the tightly coupled nature of the view and controller and introduces a new component view-model.

alt MVVM design pattern

Under MVVM, the view and view controller become formally connected and we treat them as one. Views still don’t have references to the model, but neither do controllers. Instead, they reference the view model.

The view-model is an excellent place to put validation logic for user input, presentation logic for the view, network requests, and other miscellaneous code. The one thing that does not belong in the view model is any reference to the view itself. The logic in the view model should be just as applicable on iOS as it is on OS X. In other words, we should not import UIKit.h in your view-models.

iOS apps written using MVVM are highly testable, since the view model contains all the presentation logic and doesn’t reference the view, it can be fully tested programmatically.

The motivation behind MVVM on iOS, for me is that it reduces the complexity of one’s view controllers and makes one’s presentation logic easier to test. We’ll see how it accomplishes these goals with example.

MVVM is basically just a spruced-up version of MVC, so it’s easy to incorporated into an existing app with a typical MVC architecture. Let’s take a simple Person model and corresponding view controller:

@interface Person : NSObject

- (instancetype)initwithSalutation:(NSString *)salutation firstName:(NSString *)firstName lastName:(NSString *)lastName birthdate:(NSDate *)birthdate;

@property (nonatomic, readonly) NSString *salutation;
@property (nonatomic, readonly) NSString *firstName;
@property (nonatomic, readonly) NSString *lastName;
@property (nonatomic, readonly) NSDate *birthdate;

@end

Now let’s say that we have a PersonViewController, in viewDidLoad, just sets some labels based on its model property:

- (void)viewDidLoad {
[super viewDidLoad];

if (self.model.salutation.length > 0) {
self.nameLabel.text = [NSString stringWithFormat:@"%@ %@ %@", self.model.salutation, self.model.firstName, self.model.lastName];
} else {
self.nameLabel.text = [NSString stringWithFormat:@"%@ %@", self.model.firstName, self.model.lastName];
}

NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"EEEE MMMM d, yyyy"];
self.birthdateLabel.text = [dateFormatter stringFromDate:model.birthdate];
}

Now let’s see how we can augment this with a view-model:

@interface PersonViewModel : NSObject

- (instancetype)initWithPerson:(Person *)person;

@property (nonatomic, readonly) Person *person;
@property (nonatomic, readonly) NSString *nameText;
@property (nonatomic, readonly) NSString *birthdateText;

@end

Our view model’s implementation would look like the following:

@implementation PersonViewModel

- (instancetype)initWithPerson:(Person *)person {
self = [super init];
if (!self) return nil;

_person = person;
if (person.salutation.length > 0) {
_nameText = [NSString stringWithFormat:@"%@ %@ %@", self.person.salutation, self.person.firstName, self.person.lastName];
} else {
_nameText = [NSString stringWithFormat:@"%@ %@", self.person.firstName, self.person.lastName];
}

NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"EEEE MMMM d, yyyy"];
_birthdateText = [dateFormatter stringFromDate:person.birthdate];

return self;
}

@end

We’ve moved the presentation logic in viewDidLoad into our view model. Our new viewDidLoad method is now very lightweight:

- (void)viewDidLoad {
[super viewDidLoad];

self.nameLabel.text = self.viewModel.nameText;
self.birthdateLabel.text = self.viewModel.birthdateText;
}

So, as we can see, not a lot changed from our MVC architecture. It’s the same code, just moved around. It’s compatible with MVC, leads to lighter view controllers, and is more testable.