Understanding MVC and MVVM Patterns in iOS Development
This article provides a comprehensive guide to the MVC and MVVM architectural patterns for iOS, explaining their components, interactions, KVO usage, and includes complete Objective‑C code examples that demonstrate how to implement both patterns in a real‑world news‑list app.
The article begins with a preface urging readers to invest time in the extensive textual explanation of MVC and MVVM design patterns for iOS development.
MVC Overview
It introduces the classic MVC diagram from Stanford, describing the responsibilities of Model (data management), View (UI rendering), and Controller (coordination). The text emphasizes that most apps simply display data and handle user interactions, which already follows MVC implicitly.
Key interactions are explained:
Model provides data and does not know about the View.
View displays UI elements and forwards events.
Controller mediates by assigning Model data to View and handling user actions.
The article highlights the target‑action mechanism, delegate and dataSource protocols, and the importance of keeping View and Model decoupled.
KVO and Notification
Key‑Value Observing (KVO) is presented as a way for the Controller to react to Model changes without the Model needing to know about the Controller. Notification is mentioned for broader event broadcasting.
MVC Code Demo
@interface DHNewsModel : NSObject
@property (nonatomic, strong, readonly) id dataList;
- (void)getData;
@end
@implementation DHNewsModel
- (void)getData {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.dataList = @[@{ @"title":@"新闻一", @"date":@"2016-01-25", @"image":@"http://...jpg", @"content":@"blablabla" },
@{ @"title":@"新闻二", @"date":@"2016-01-27", @"image":@"http://...jpg", @"content":@"ahahaha" }];
});
}
@end #import "ViewController.h"
#import "DHNewsModel.h"
@interface ViewController ()
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) DHNewsModel *model;
- (void)_registerObeserver;
- (void)_unregisterObserver;
@end
@implementation ViewController
- (void)dealloc { [self _unregisterObserver]; }
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.tableView];
[self.model getData];
[self _registerObeserver];
}
- (void)_registerObeserver {
[self.model addObserver:self forKeyPath:@"dataList" options:NSKeyValueObservingOptionNew context:nil];
}
- (void)_unregisterObserver {
[self.model removeObserver:self forKeyPath:@"dataList"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
[self.tableView reloadData];
}
- (UITableView *)tableView {
if (!_tableView) {
_tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
_tableView.dataSource = self;
_tableView.delegate = self;
_tableView.tableFooterView = [[UIView alloc] init];
}
return _tableView;
}
- (DHNewsModel *)model {
if (!_model) { _model = [[DHNewsModel alloc] init]; }
return _model;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.model.dataList count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cellIdf"];
if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cellIdf"]; }
NSDictionary *infoDic = self.model.dataList[indexPath.row];
cell.textLabel.text = infoDic[@"title"];
cell.detailTextLabel.text = infoDic[@"date"];
NSURL *imageUrl = [NSURL URLWithString:infoDic[@"image"]];
dispatch_async(dispatch_get_global_queue(0,0), ^{
NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
UIImage *image = [UIImage imageWithData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{ cell.imageView.image = image; });
});
return cell;
}
@endTransition to MVVM
The article explains why MVVM was introduced: to separate data parsing logic from the Controller, reducing its responsibilities. It defines Model, View, and ViewModel, and stresses that the ViewModel handles data transformation while the Controller merely binds to the ViewModel.
MVVM Code Demo
#import "DHNewsViewModel.h"
#import "DHNewsModel.h"
#import
@interface DHNewsViewModel ()
@property (nonatomic, strong) DHNewsModel *model;
@end
@implementation DHNewsViewModel
- (NSString *)observingKeyPath { return @"model.dataList"; }
- (void)getData { [self.model getData]; }
- (NSInteger)numberOfRowsInSection:(NSInteger)section { return [self.model.dataList count]; }
- (NSString *)cellTitleAtIndexPath:(NSIndexPath *)indexPath { return self.model.dataList[indexPath.row][@"title"]; }
- (NSString *)cellDateAtIndexPath:(NSIndexPath *)indexPath { return self.model.dataList[indexPath.row][@"date"]; }
- (NSURL *)cellImageUrlAtIndexPath:(NSIndexPath *)indexPath { return [NSURL URLWithString:self.model.dataList[indexPath.row][@"image"]]; }
- (DHNewsModel *)model {
if (!_model) { _model = [[DHNewsModel alloc] init]; }
return _model;
}
@end #import "ViewController.h"
#import "DHNewsViewModel.h"
@interface ViewController ()
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) DHNewsViewModel *viewModel;
- (void)_registerObeserver;
- (void)_unregisterObserver;
@end
@implementation ViewController
- (void)dealloc { [self _unregisterObserver]; }
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.tableView];
[self.viewModel getData];
[self _registerObeserver];
}
- (void)_registerObeserver {
[self.viewModel addObserver:self forKeyPath:[self.viewModel observingKeyPath] options:NSKeyValueObservingOptionNew context:nil];
}
- (void)_unregisterObserver {
[self.viewModel removeObserver:self forKeyPath:[self.viewModel observingKeyPath]];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
[self.tableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.viewModel numberOfRowsInSection:section];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cellIdf"];
if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cellIdf"]; }
cell.textLabel.text = [self.viewModel cellTitleAtIndexPath:indexPath];
cell.detailTextLabel.text = [self.viewModel cellDateAtIndexPath:indexPath];
NSURL *imageUrl = [self.viewModel cellImageUrlAtIndexPath:indexPath];
dispatch_async(dispatch_get_global_queue(0,0), ^{
NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
UIImage *image = [UIImage imageWithData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{ cell.imageView.image = image; });
});
return cell;
}
- (UITableView *)tableView {
if (!_tableView) {
_tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
_tableView.dataSource = self;
_tableView.delegate = self;
_tableView.tableFooterView = [[UIView alloc] init];
}
return _tableView;
}
- (DHNewsViewModel *)viewModel {
if (!_viewModel) { _viewModel = [[DHNewsViewModel alloc] init]; }
return _viewModel;
}
@endFinally, the article concludes that repeated practice with MVVM will solidify understanding of the pattern and improve object‑oriented thinking, and suggests a simple exercise of displaying the current system time using Model‑View‑ViewModel.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.