SlideShare une entreprise Scribd logo
1  sur  97
Télécharger pour lire hors ligne
Templates Considered Harmful
Apple Templates and the Single Responsibility
Principle
Brian Gesiak
March 28th, 2014 at Recruit Co., Ltd.
Research Student, The University of Tokyo
@modocache
Today
• The single responsibility principle (SRP)
• The UITableViewController file template
• Model and controller code all rolled into one
• Example of how to separate concerns
• Moving data store logic out of the controller in
GitHubViewer.app
• Apple file and project templates
• Many violate the single responsibility principle
• Best to think of them as“proof of concepts”
Single Responsibility Principle
A class should have one, and only one,
reason to change.
- Robert C. Martin
Robert C. Martin (Uncle Bob)
UITableViewController
#pragma mark - UIViewController
- (id)initWithStyle:(UITableViewStyle)style { /* ... */ }
- (void)viewDidLoad { /* ... */ }
- (void)didReceiveMemoryWarning { /* ... */ }
- (void)prepareForSegue:(UIStoryboardSegue *)segue
sender:(id)sender { /* ... */ }
!
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:
(UITableView *)tableView { /* ... */ }
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section { /* ... */ }
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{ /* ... */ }
!
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ }
What’s Included in the Template
UITableViewController
#pragma mark - UIViewController
- (id)initWithStyle:(UITableViewStyle)style { /* ... */ }
- (void)viewDidLoad { /* ... */ }
- (void)didReceiveMemoryWarning { /* ... */ }
- (void)prepareForSegue:(UIStoryboardSegue *)segue
sender:(id)sender { /* ... */ }
!
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:
(UITableView *)tableView { /* ... */ }
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section { /* ... */ }
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{ /* ... */ }
!
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ }
What’s Included in the Template
UITableViewController
#pragma mark - UIViewController
- (id)initWithStyle:(UITableViewStyle)style { /* ... */ }
- (void)viewDidLoad { /* ... */ }
- (void)didReceiveMemoryWarning { /* ... */ }
- (void)prepareForSegue:(UIStoryboardSegue *)segue
sender:(id)sender { /* ... */ }
!
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:
(UITableView *)tableView { /* ... */ }
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section { /* ... */ }
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{ /* ... */ }
!
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ }
What’s Included in the Template
UITableViewController
#pragma mark - UIViewController
- (id)initWithStyle:(UITableViewStyle)style { /* ... */ }
- (void)viewDidLoad { /* ... */ }
- (void)didReceiveMemoryWarning { /* ... */ }
- (void)prepareForSegue:(UIStoryboardSegue *)segue
sender:(id)sender { /* ... */ }
!
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:
(UITableView *)tableView { /* ... */ }
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section { /* ... */ }
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{ /* ... */ }
!
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ }
What’s Included in the Template
UITableViewDelegate
UITableViewDataSource
UITableViewController
Responsibilities, a.k.a.“Reasons to Change”
1. Determines what data in displayed in the table
!
!
2. Determines how users interact with the table
UITableViewDelegate
UITableViewDataSource
UITableViewController
Responsibilities, a.k.a.“Reasons to Change”
1. Determines what data in displayed in the table
!
!
2. Determines how users interact with the table
UITableViewDelegate
UITableViewDataSource
UITableViewController
Responsibilities, a.k.a.“Reasons to Change”
1. Determines what data in displayed in the table
!
!
2. Determines how users interact with the table
UITableViewDelegate
UITableViewDataSource
UITableViewController
Responsibilities, a.k.a.“Reasons to Change”
1. Determines what data in displayed in the table
!
!
2. Determines how users interact with the table
GitHubViewer
GitHub icon by @peterhajas,
licensed under the Creative
Commons License version 3.0.
GitHubViewer
GitHub icon by @peterhajas,
licensed under the Creative
Commons License version 3.0.
GHVReposViewController
- (void)viewDidLoad {
[super viewDidLoad];
!
self.title = NSLocalizedString(@"Repositories", nil);
[self getRepositories];
}
View Controller Responsibilities
GHVReposViewController
- (void)viewDidLoad {
[super viewDidLoad];
!
self.title = NSLocalizedString(@"Repositories", nil);
[self getRepositories];
}
View Controller Responsibilities
GHVReposViewController
- (void)getRepositories {
[self startNetworkIndicators];
[self.apiClient
allRepositoriesForUsername:self.username
success:^(NSArray *repositories) {
[self stopNetworkIndicators];
self.repositories = repositories;
[self.tableView reloadData];
} failure:^(NSError *error) {
[self stopNetworkIndicators];
[UIAlertView showAlertForError:error];
}];
}
View Controller Responsibilities
GHVReposViewController
- (void)getRepositories {
[self startNetworkIndicators];
[self.apiClient
allRepositoriesForUsername:self.username
success:^(NSArray *repositories) {
[self stopNetworkIndicators];
self.repositories = repositories;
[self.tableView reloadData];
} failure:^(NSError *error) {
[self stopNetworkIndicators];
[UIAlertView showAlertForError:error];
}];
}
View Controller Responsibilities
GHVReposViewController
- (void)getRepositories {
[self startNetworkIndicators];
[self.apiClient
allRepositoriesForUsername:self.username
success:^(NSArray *repositories) {
[self stopNetworkIndicators];
self.repositories = repositories;
[self.tableView reloadData];
} failure:^(NSError *error) {
[self stopNetworkIndicators];
[UIAlertView showAlertForError:error];
}];
}
View Controller Responsibilities
GHVReposViewController
- (void)getRepositories {
[self startNetworkIndicators];
[self.apiClient
allRepositoriesForUsername:self.username
success:^(NSArray *repositories) {
[self stopNetworkIndicators];
self.repositories = repositories;
[self.tableView reloadData];
} failure:^(NSError *error) {
[self stopNetworkIndicators];
[UIAlertView showAlertForError:error];
}];
}
View Controller Responsibilities
GHVReposViewController
- (void)getRepositories {
[self startNetworkIndicators];
[self.apiClient
allRepositoriesForUsername:self.username
success:^(NSArray *repositories) {
[self stopNetworkIndicators];
self.repositories = repositories;
[self.tableView reloadData];
} failure:^(NSError *error) {
[self stopNetworkIndicators];
[UIAlertView showAlertForError:error];
}];
}
View Controller Responsibilities
GHVReposViewController
- (void)getRepositories {
[self startNetworkIndicators];
[self.apiClient
allRepositoriesForUsername:self.username
success:^(NSArray *repositories) {
[self stopNetworkIndicators];
self.repositories = repositories;
[self.tableView reloadData];
} failure:^(NSError *error) {
[self stopNetworkIndicators];
[UIAlertView showAlertForError:error];
}];
}
View Controller Responsibilities
GHVReposViewController
- (void)getRepositories {
[self startNetworkIndicators];
[self.apiClient
allRepositoriesForUsername:self.username
success:^(NSArray *repositories) {
[self stopNetworkIndicators];
self.repositories = repositories;
[self.tableView reloadData];
} failure:^(NSError *error) {
[self stopNetworkIndicators];
[UIAlertView showAlertForError:error];
}];
}
View Controller Responsibilities
GHVReposViewController
- (void)getRepositories {
[self startNetworkIndicators];
[self.apiClient
allRepositoriesForUsername:self.username
success:^(NSArray *repositories) {
[self stopNetworkIndicators];
self.repositories = repositories;
[self.tableView reloadData];
} failure:^(NSError *error) {
[self stopNetworkIndicators];
[UIAlertView showAlertForError:error];
}];
}
View Controller Responsibilities
GHVReposViewController
- (void)getRepositories {
[self startNetworkIndicators];
[self.apiClient
allRepositoriesForUsername:self.username
success:^(NSArray *repositories) {
[self stopNetworkIndicators];
self.repositories = repositories;
[self.tableView reloadData];
} failure:^(NSError *error) {
[self stopNetworkIndicators];
[UIAlertView showAlertForError:error];
}];
}
View Controller Responsibilities
GHVReposViewController
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
!
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [self.repositories count];
}
!
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:kGHVRepoCellIdentifier];
}
!
GHVRepo *repository = self.repositories[indexPath.row];
cell.textLabel.text = repository.name;
cell.detailTextLabel.text = repository.repositoryDescription;
return cell;
}
UITableViewDataSource Responsibilities
GHVReposViewController
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
!
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [self.repositories count];
}
!
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:kGHVRepoCellIdentifier];
}
!
GHVRepo *repository = self.repositories[indexPath.row];
cell.textLabel.text = repository.name;
cell.detailTextLabel.text = repository.repositoryDescription;
return cell;
}
UITableViewDataSource Responsibilities
GHVReposViewController
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
!
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [self.repositories count];
}
!
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:kGHVRepoCellIdentifier];
}
!
GHVRepo *repository = self.repositories[indexPath.row];
cell.textLabel.text = repository.name;
cell.detailTextLabel.text = repository.repositoryDescription;
return cell;
}
UITableViewDataSource Responsibilities
GHVReposViewController
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
!
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [self.repositories count];
}
!
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:kGHVRepoCellIdentifier];
}
!
GHVRepo *repository = self.repositories[indexPath.row];
cell.textLabel.text = repository.name;
cell.detailTextLabel.text = repository.repositoryDescription;
return cell;
}
UITableViewDataSource Responsibilities
GHVReposViewController
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
!
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [self.repositories count];
}
!
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:kGHVRepoCellIdentifier];
}
!
GHVRepo *repository = self.repositories[indexPath.row];
cell.textLabel.text = repository.name;
cell.detailTextLabel.text = repository.repositoryDescription;
return cell;
}
UITableViewDataSource Responsibilities
GHVReposViewController
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
!
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [self.repositories count];
}
!
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:kGHVRepoCellIdentifier];
}
!
GHVRepo *repository = self.repositories[indexPath.row];
cell.textLabel.text = repository.name;
cell.detailTextLabel.text = repository.repositoryDescription;
return cell;
}
UITableViewDataSource Responsibilities
GHVReposViewController
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
!
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [self.repositories count];
}
!
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:kGHVRepoCellIdentifier];
}
!
GHVRepo *repository = self.repositories[indexPath.row];
cell.textLabel.text = repository.name;
cell.detailTextLabel.text = repository.repositoryDescription;
return cell;
}
UITableViewDataSource Responsibilities
GHVReposViewController
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
!
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [self.repositories count];
}
!
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:kGHVRepoCellIdentifier];
}
!
GHVRepo *repository = self.repositories[indexPath.row];
cell.textLabel.text = repository.name;
cell.detailTextLabel.text = repository.repositoryDescription;
return cell;
}
UITableViewDataSource Responsibilities
GHVReposViewController
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
!
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [self.repositories count];
}
!
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:kGHVRepoCellIdentifier];
}
!
GHVRepo *repository = self.repositories[indexPath.row];
cell.textLabel.text = repository.name;
cell.detailTextLabel.text = repository.repositoryDescription;
return cell;
}
UITableViewDataSource Responsibilities
GHVReposViewController
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
GHVRepo *repository = self.repositories[indexPath.row];
GHVRepoViewController *controller =
[[GHVRepoViewController alloc] initWithRepository:repository];
[self.navigationController pushViewController:controller
animated:YES];
}
UITableViewDelegate Responsibilities
GHVReposViewController
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
GHVRepo *repository = self.repositories[indexPath.row];
GHVRepoViewController *controller =
[[GHVRepoViewController alloc] initWithRepository:repository];
[self.navigationController pushViewController:controller
animated:YES];
}
UITableViewDelegate Responsibilities
GHVReposViewController
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
GHVRepo *repository = self.repositories[indexPath.row];
GHVRepoViewController *controller =
[[GHVRepoViewController alloc] initWithRepository:repository];
[self.navigationController pushViewController:controller
animated:YES];
}
UITableViewDelegate Responsibilities
GHVReposViewController
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
GHVRepo *repository = self.repositories[indexPath.row];
GHVRepoViewController *controller =
[[GHVRepoViewController alloc] initWithRepository:repository];
[self.navigationController pushViewController:controller
animated:YES];
}
UITableViewDelegate Responsibilities
GHVReposViewController
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
GHVRepo *repository = self.repositories[indexPath.row];
GHVRepoViewController *controller =
[[GHVRepoViewController alloc] initWithRepository:repository];
[self.navigationController pushViewController:controller
animated:YES];
}
UITableViewDelegate Responsibilities
GHVReposViewController
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
GHVRepo *repository = self.repositories[indexPath.row];
GHVRepoViewController *controller =
[[GHVRepoViewController alloc] initWithRepository:repository];
[self.navigationController pushViewController:controller
animated:YES];
}
UITableViewDelegate Responsibilities
GHVReposViewController
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
GHVRepo *repository = self.repositories[indexPath.row];
GHVRepoViewController *controller =
[[GHVRepoViewController alloc] initWithRepository:repository];
[self.navigationController pushViewController:controller
animated:YES];
}
UITableViewDelegate Responsibilities
GHVReposViewController
GHVReposViewController
1. View controller responsibilities
GHVReposViewController
1. View controller responsibilities
• Kick off repository fetch
GHVReposViewController
1. View controller responsibilities
• Kick off repository fetch
• Show network activity indicators
GHVReposViewController
1. View controller responsibilities
• Kick off repository fetch
• Show network activity indicators
2. UITableViewDataSource responsibilities
GHVReposViewController
1. View controller responsibilities
• Kick off repository fetch
• Show network activity indicators
2. UITableViewDataSource responsibilities
• Store repositories
GHVReposViewController
1. View controller responsibilities
• Kick off repository fetch
• Show network activity indicators
2. UITableViewDataSource responsibilities
• Store repositories
• Specify how many sections in table view
GHVReposViewController
1. View controller responsibilities
• Kick off repository fetch
• Show network activity indicators
2. UITableViewDataSource responsibilities
• Store repositories
• Specify how many sections in table view
• Specify how many rows in each section
GHVReposViewController
1. View controller responsibilities
• Kick off repository fetch
• Show network activity indicators
2. UITableViewDataSource responsibilities
• Store repositories
• Specify how many sections in table view
• Specify how many rows in each section
• Create each cell based on repository data
GHVReposViewController
1. View controller responsibilities
• Kick off repository fetch
• Show network activity indicators
2. UITableViewDataSource responsibilities
• Store repositories
• Specify how many sections in table view
• Specify how many rows in each section
• Create each cell based on repository data
3. UITableViewDelegate responsibilities
GHVReposViewController
1. View controller responsibilities
• Kick off repository fetch
• Show network activity indicators
2. UITableViewDataSource responsibilities
• Store repositories
• Specify how many sections in table view
• Specify how many rows in each section
• Create each cell based on repository data
3. UITableViewDelegate responsibilities
• Push view controller for selected repository
GHVReposViewController
1. View controller responsibilities
• Kick off repository fetch
• Show network activity indicators
2. UITableViewDataSource responsibilities
• Store repositories
• Specify how many sections in table view
• Specify how many rows in each section
• Create each cell based on repository data
3. UITableViewDelegate responsibilities
• Push view controller for selected repository
100+ lines of code
GHVReposViewController
1. View controller responsibilities
• Kick off repository fetch
• Show network activity indicators
2. UITableViewDataSource responsibilities
• Store repositories
• Specify how many sections in table view
• Specify how many rows in each section
• Create each cell based on repository data
3. UITableViewDelegate responsibilities
• Push view controller for selected repository
100+ lines of code
GHVReposViewController
What a Bloated View Controller Looks Like
GHVReposViewController
Separation of Concerns
GHVReposViewController
Separation of Concerns
GHVReposViewController
Separation of Concerns
GHVRepoStore
A UITableViewDataStore and Nothing More
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
!
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [self.repositories count];
}
!
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
/// Dequeue, configure, and return a cell
}
!
- (void)reloadRepositories:(GHVRepoStoreSuccessBlock)success
failure:(GHVRepoStoreFailureBlock)failure {
[self.apiClient allRepositoriesForUsername:self.username
success:/* ... */
failure:/* ... */];
}
GHVRepoStore
A UITableViewDataStore and Nothing More
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
!
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [self.repositories count];
}
!
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
/// Dequeue, configure, and return a cell
}
!
- (void)reloadRepositories:(GHVRepoStoreSuccessBlock)success
failure:(GHVRepoStoreFailureBlock)failure {
[self.apiClient allRepositoriesForUsername:self.username
success:/* ... */
failure:/* ... */];
}
GHVRepoStore
A UITableViewDataStore and Nothing More
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
!
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [self.repositories count];
}
!
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
/// Dequeue, configure, and return a cell
}
!
- (void)reloadRepositories:(GHVRepoStoreSuccessBlock)success
failure:(GHVRepoStoreFailureBlock)failure {
[self.apiClient allRepositoriesForUsername:self.username
success:/* ... */
failure:/* ... */];
}
GHVRepoStore
A UITableViewDataStore and Nothing More
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
!
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [self.repositories count];
}
!
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
/// Dequeue, configure, and return a cell
}
!
- (void)reloadRepositories:(GHVRepoStoreSuccessBlock)success
failure:(GHVRepoStoreFailureBlock)failure {
[self.apiClient allRepositoriesForUsername:self.username
success:/* ... */
failure:/* ... */];
}
GHVRepoStore
A UITableViewDataStore and Nothing More
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
!
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [self.repositories count];
}
!
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
/// Dequeue, configure, and return a cell
}
!
- (void)reloadRepositories:(GHVRepoStoreSuccessBlock)success
failure:(GHVRepoStoreFailureBlock)failure {
[self.apiClient allRepositoriesForUsername:self.username
success:/* ... */
failure:/* ... */];
}
GHVReposViewController
Transferring Responsibilities to the Store
- (id)initWithRepoStore:(GHVRepoStore *)repoStore {
self = [super initWithStyle:UITableViewStylePlain];
if (self) {
_repoStore = repoStore;
}
return self;
}
!
- (void)viewDidLoad {
[super viewDidLoad];
!
self.title = NSLocalizedString(@"Repositories", nil);
[self getRepositories];
!
!
!
!
!
!
!
!
}
GHVReposViewController
Transferring Responsibilities to the Store
- (id)initWithRepoStore:(GHVRepoStore *)repoStore {
self = [super initWithStyle:UITableViewStylePlain];
if (self) {
_repoStore = repoStore;
}
return self;
}
!
- (void)viewDidLoad {
[super viewDidLoad];
!
self.title = NSLocalizedString(@"Repositories", nil);
[self getRepositories];
!
!
!
!
!
!
!
!
}
GHVReposViewController
Transferring Responsibilities to the Store
- (id)initWithRepoStore:(GHVRepoStore *)repoStore {
self = [super initWithStyle:UITableViewStylePlain];
if (self) {
_repoStore = repoStore;
}
return self;
}
!
- (void)viewDidLoad {
[super viewDidLoad];
!
self.title = NSLocalizedString(@"Repositories", nil);
[self getRepositories];
!
!
!
!
!
!
!
!
}
GHVReposViewController
Transferring Responsibilities to the Store
- (id)initWithRepoStore:(GHVRepoStore *)repoStore {
self = [super initWithStyle:UITableViewStylePlain];
if (self) {
_repoStore = repoStore;
}
return self;
}
!
- (void)viewDidLoad {
[super viewDidLoad];
!
self.title = NSLocalizedString(@"Repositories", nil);
[self getRepositories];
!
!
!
!
!
!
!
!
}
GHVReposViewController
Transferring Responsibilities to the Store
- (id)initWithRepoStore:(GHVRepoStore *)repoStore {
self = [super initWithStyle:UITableViewStylePlain];
if (self) {
_repoStore = repoStore;
}
return self;
}
!
- (void)viewDidLoad {
[super viewDidLoad];
!
self.title = NSLocalizedString(@"Repositories", nil);
[self getRepositories];
!
!
!
!
!
!
!
!
}
GHVReposViewController
Transferring Responsibilities to the Store
- (id)initWithRepoStore:(GHVRepoStore *)repoStore {
self = [super initWithStyle:UITableViewStylePlain];
if (self) {
_repoStore = repoStore;
}
return self;
}
!
- (void)viewDidLoad {
[super viewDidLoad];
!
self.title = NSLocalizedString(@"Repositories", nil);
[self getRepositories];
!
!
!
!
!
!
!
!
}
GHVReposViewController
Transferring Responsibilities to the Store
- (id)initWithRepoStore:(GHVRepoStore *)repoStore {
self = [super initWithStyle:UITableViewStylePlain];
if (self) {
_repoStore = repoStore;
}
return self;
}
!
- (void)viewDidLoad {
[super viewDidLoad];
!
self.title = NSLocalizedString(@"Repositories", nil);
[self getRepositories];
!
!
!
!
!
!
!
!
}
self.tableView.dataSource = self.repoStore;
[self startNetworkIndicators];
[self.repoStore reloadRepositories:^{
[self stopNetworkIndicators];
[self.tableView reloadData];
} failure:^(NSError *error) {
[self stopNetworkIndicators];
[UIAlertView showAlertForError:error];
}];
GHVReposViewController
Transferring Responsibilities to the Store
- (id)initWithRepoStore:(GHVRepoStore *)repoStore {
self = [super initWithStyle:UITableViewStylePlain];
if (self) {
_repoStore = repoStore;
}
return self;
}
!
- (void)viewDidLoad {
[super viewDidLoad];
!
self.title = NSLocalizedString(@"Repositories", nil);
[self getRepositories];
!
!
!
!
!
!
!
!
}
self.tableView.dataSource = self.repoStore;
[self startNetworkIndicators];
[self.repoStore reloadRepositories:^{
[self stopNetworkIndicators];
[self.tableView reloadData];
} failure:^(NSError *error) {
[self stopNetworkIndicators];
[UIAlertView showAlertForError:error];
}];
GHVReposViewController
Transferring Responsibilities to the Store
- (id)initWithRepoStore:(GHVRepoStore *)repoStore {
self = [super initWithStyle:UITableViewStylePlain];
if (self) {
_repoStore = repoStore;
}
return self;
}
!
- (void)viewDidLoad {
[super viewDidLoad];
!
self.title = NSLocalizedString(@"Repositories", nil);
[self getRepositories];
!
!
!
!
!
!
!
!
}
self.tableView.dataSource = self.repoStore;
[self startNetworkIndicators];
[self.repoStore reloadRepositories:^{
[self stopNetworkIndicators];
[self.tableView reloadData];
} failure:^(NSError *error) {
[self stopNetworkIndicators];
[UIAlertView showAlertForError:error];
}];
GHVReposViewController
Transferring Responsibilities to the Store
- (id)initWithRepoStore:(GHVRepoStore *)repoStore {
self = [super initWithStyle:UITableViewStylePlain];
if (self) {
_repoStore = repoStore;
}
return self;
}
!
- (void)viewDidLoad {
[super viewDidLoad];
!
self.title = NSLocalizedString(@"Repositories", nil);
[self getRepositories];
!
!
!
!
!
!
!
!
}
self.tableView.dataSource = self.repoStore;
[self startNetworkIndicators];
[self.repoStore reloadRepositories:^{
[self stopNetworkIndicators];
[self.tableView reloadData];
} failure:^(NSError *error) {
[self stopNetworkIndicators];
[UIAlertView showAlertForError:error];
}];
GHVReposViewController
Transferring Responsibilities to the Store
- (id)initWithRepoStore:(GHVRepoStore *)repoStore {
self = [super initWithStyle:UITableViewStylePlain];
if (self) {
_repoStore = repoStore;
}
return self;
}
!
- (void)viewDidLoad {
[super viewDidLoad];
!
self.title = NSLocalizedString(@"Repositories", nil);
[self getRepositories];
!
!
!
!
!
!
!
!
}
self.tableView.dataSource = self.repoStore;
[self startNetworkIndicators];
[self.repoStore reloadRepositories:^{
[self stopNetworkIndicators];
[self.tableView reloadData];
} failure:^(NSError *error) {
[self stopNetworkIndicators];
[UIAlertView showAlertForError:error];
}];
GHVReposViewController
Transferring Responsibilities to the Store
- (id)initWithRepoStore:(GHVRepoStore *)repoStore {
self = [super initWithStyle:UITableViewStylePlain];
if (self) {
_repoStore = repoStore;
}
return self;
}
!
- (void)viewDidLoad {
[super viewDidLoad];
!
self.title = NSLocalizedString(@"Repositories", nil);
[self getRepositories];
!
!
!
!
!
!
!
!
}
self.tableView.dataSource = self.repoStore;
[self startNetworkIndicators];
[self.repoStore reloadRepositories:^{
[self stopNetworkIndicators];
[self.tableView reloadData];
} failure:^(NSError *error) {
[self stopNetworkIndicators];
[UIAlertView showAlertForError:error];
}];
GHVReposViewController
Transferring Responsibilities to the Store
- (id)initWithRepoStore:(GHVRepoStore *)repoStore {
self = [super initWithStyle:UITableViewStylePlain];
if (self) {
_repoStore = repoStore;
}
return self;
}
!
- (void)viewDidLoad {
[super viewDidLoad];
!
self.title = NSLocalizedString(@"Repositories", nil);
[self getRepositories];
!
!
!
!
!
!
!
!
}
self.tableView.dataSource = self.repoStore;
[self startNetworkIndicators];
[self.repoStore reloadRepositories:^{
[self stopNetworkIndicators];
[self.tableView reloadData];
} failure:^(NSError *error) {
[self stopNetworkIndicators];
[UIAlertView showAlertForError:error];
}];
GHVReposViewController
Transferring Responsibilities to the Store
- (id)initWithRepoStore:(GHVRepoStore *)repoStore {
self = [super initWithStyle:UITableViewStylePlain];
if (self) {
_repoStore = repoStore;
}
return self;
}
!
- (void)viewDidLoad {
[super viewDidLoad];
!
self.title = NSLocalizedString(@"Repositories", nil);
[self getRepositories];
!
!
!
!
!
!
!
!
}
self.tableView.dataSource = self.repoStore;
[self startNetworkIndicators];
[self.repoStore reloadRepositories:^{
[self stopNetworkIndicators];
[self.tableView reloadData];
} failure:^(NSError *error) {
[self stopNetworkIndicators];
[UIAlertView showAlertForError:error];
}];
GHVReposViewController
Transferring Responsibilities to the Store
- (id)initWithRepoStore:(GHVRepoStore *)repoStore {
self = [super initWithStyle:UITableViewStylePlain];
if (self) {
_repoStore = repoStore;
}
return self;
}
!
- (void)viewDidLoad {
[super viewDidLoad];
!
self.title = NSLocalizedString(@"Repositories", nil);
[self getRepositories];
!
!
!
!
!
!
!
!
}
self.tableView.dataSource = self.repoStore;
[self startNetworkIndicators];
[self.repoStore reloadRepositories:^{
[self stopNetworkIndicators];
[self.tableView reloadData];
} failure:^(NSError *error) {
[self stopNetworkIndicators];
[UIAlertView showAlertForError:error];
}];
GHVReposViewController
GHVReposViewController
1. View controller responsibilities
GHVReposViewController
1. View controller responsibilities
• Connect table view to data store
GHVReposViewController
1. View controller responsibilities
• Connect table view to data store
• Show network activity indicators
GHVReposViewController
1. View controller responsibilities
• Connect table view to data store
• Show network activity indicators
2. UITableViewDelegate responsibilities
GHVReposViewController
1. View controller responsibilities
• Connect table view to data store
• Show network activity indicators
2. UITableViewDelegate responsibilities
• Push view controller for selected repository
GHVReposViewController
1. View controller responsibilities
• Connect table view to data store
• Show network activity indicators
2. UITableViewDelegate responsibilities
• Push view controller for selected repository
Less than 100 lines of code
Single Responsibility Principle
Benefits
Single Responsibility Principle
• Less brittle
Benefits
Single Responsibility Principle
• Less brittle
• More modular
Benefits
Single Responsibility Principle
• Less brittle
• More modular
• Easier to read, understand, and debug
Benefits
The Problem with Apple Templates
If Only They Knew About the SRP
The Problem with Apple Templates
If Only They Knew About the SRP
• Master-Detail Application (with Core Data)
• AppDelegate
• Sets up UIWindow root view controller
• Sets up Core Data stack, handles errors
• 100+ lines of code
The Problem with Apple Templates
If Only They Knew About the SRP
• Master-Detail Application (with Core Data)
• AppDelegate
• Sets up UIWindow root view controller
• Sets up Core Data stack, handles errors
• 100+ lines of code
• OpenGL Application
• View controller does it all!
• Sets up OpenGL context
• Compiles shaders
• Stores vertex data
• 400+ lines of code
Not All Templates Created Equal
Some Templates Employ Modular Design
Not All Templates Created Equal
Some Templates Employ Modular Design
• Page-Based Application
• Separates concerns among UIPageViewDelegate and
UIPageViewDataSource
• Small classes
Not All Templates Created Equal
Some Templates Employ Modular Design
• Page-Based Application
• Separates concerns among UIPageViewDelegate and
UIPageViewDataSource
• Small classes
• SpriteKit Game
• Small classes with a relatively clear separation of
concerns
Takeaways
• Single responsibility principle
• Classes should have one, and only one, reason to
change
• Apple’s templates should be thought of as“proof of
concepts”, not as examples of clean, well-structured code
• Just because Apple does it doesn’t mean it’s a good idea
• Use your better judgement on what’s“clean code”and
what isn’t
Want More on Clean Code?
• Slides for this talk available at http://modocache.io/
apple-templates-considered-harmful
• Follow me on Twitter and GitHub at @modocache
• Clean Code Resources
• Follow Robert C. Martin at @unclebobmartin
• More on Object-Oriented Design at http://
www.butunclebob.com/
ArticleS.UncleBob.PrinciplesOfOod
• Clean Code videos at http://cleancoders.com/
• Building a Healthy Mistrust of Apple Engineering
• Follow Peter Steinberger at @steipete
• Follow Justin Spahr-Summers at @ jspahrsummers

Contenu connexe

Tendances

Spring & Hibernate
Spring & HibernateSpring & Hibernate
Spring & HibernateJiayun Zhou
 
SymfonyCon Berlin 2016 Jenkins Deployment Pipelines
SymfonyCon Berlin 2016 Jenkins Deployment PipelinesSymfonyCon Berlin 2016 Jenkins Deployment Pipelines
SymfonyCon Berlin 2016 Jenkins Deployment Pipelinescpsitgmbh
 
Continous delivery with sbt
Continous delivery with sbtContinous delivery with sbt
Continous delivery with sbtWojciech Pituła
 
Basic Tutorial of React for Programmers
Basic Tutorial of React for ProgrammersBasic Tutorial of React for Programmers
Basic Tutorial of React for ProgrammersDavid Rodenas
 
Arquillian Constellation
Arquillian ConstellationArquillian Constellation
Arquillian ConstellationAlex Soto
 
Containers & Dependency in Ember.js
Containers & Dependency in Ember.jsContainers & Dependency in Ember.js
Containers & Dependency in Ember.jsMatthew Beale
 
React, Redux and es6/7
React, Redux and es6/7React, Redux and es6/7
React, Redux and es6/7Dongho Cho
 
連邦の白いヤツ 「Objective-C」
連邦の白いヤツ 「Objective-C」連邦の白いヤツ 「Objective-C」
連邦の白いヤツ 「Objective-C」matuura_core
 
Android Design Patterns
Android Design PatternsAndroid Design Patterns
Android Design PatternsGodfrey Nolan
 
Automated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.xAutomated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.xTatsuya Maki
 
RDSDataSource: Мастер-класс по Dip
RDSDataSource: Мастер-класс по DipRDSDataSource: Мастер-класс по Dip
RDSDataSource: Мастер-класс по DipRAMBLER&Co
 
EPAM IT WEEK: AEM & TDD. It's so boring...
EPAM IT WEEK: AEM & TDD. It's so boring...EPAM IT WEEK: AEM & TDD. It's so boring...
EPAM IT WEEK: AEM & TDD. It's so boring...Andrew Manuev
 
OSGi and Eclipse RCP
OSGi and Eclipse RCPOSGi and Eclipse RCP
OSGi and Eclipse RCPEric Jain
 
Dependency Injection with CDI in 15 minutes
Dependency Injection with CDI in 15 minutesDependency Injection with CDI in 15 minutes
Dependency Injection with CDI in 15 minutesAntonio Goncalves
 
#11.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원학원,재직자/실업자교육학원,스프링교육,마이바...
#11.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원학원,재직자/실업자교육학원,스프링교육,마이바...#11.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원학원,재직자/실업자교육학원,스프링교육,마이바...
#11.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원학원,재직자/실업자교육학원,스프링교육,마이바...탑크리에듀(구로디지털단지역3번출구 2분거리)
 
Application Frameworks: The new kids on the block
Application Frameworks: The new kids on the blockApplication Frameworks: The new kids on the block
Application Frameworks: The new kids on the blockRichard Lord
 

Tendances (20)

Spring & Hibernate
Spring & HibernateSpring & Hibernate
Spring & Hibernate
 
SymfonyCon Berlin 2016 Jenkins Deployment Pipelines
SymfonyCon Berlin 2016 Jenkins Deployment PipelinesSymfonyCon Berlin 2016 Jenkins Deployment Pipelines
SymfonyCon Berlin 2016 Jenkins Deployment Pipelines
 
Continous delivery with sbt
Continous delivery with sbtContinous delivery with sbt
Continous delivery with sbt
 
Basic Tutorial of React for Programmers
Basic Tutorial of React for ProgrammersBasic Tutorial of React for Programmers
Basic Tutorial of React for Programmers
 
Arquillian Constellation
Arquillian ConstellationArquillian Constellation
Arquillian Constellation
 
Containers & Dependency in Ember.js
Containers & Dependency in Ember.jsContainers & Dependency in Ember.js
Containers & Dependency in Ember.js
 
React, Redux and es6/7
React, Redux and es6/7React, Redux and es6/7
React, Redux and es6/7
 
連邦の白いヤツ 「Objective-C」
連邦の白いヤツ 「Objective-C」連邦の白いヤツ 「Objective-C」
連邦の白いヤツ 「Objective-C」
 
Android Design Patterns
Android Design PatternsAndroid Design Patterns
Android Design Patterns
 
Automated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.xAutomated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.x
 
OneRing @ OSCamp 2010
OneRing @ OSCamp 2010OneRing @ OSCamp 2010
OneRing @ OSCamp 2010
 
The JavaFX Ecosystem
The JavaFX EcosystemThe JavaFX Ecosystem
The JavaFX Ecosystem
 
RDSDataSource: Мастер-класс по Dip
RDSDataSource: Мастер-класс по DipRDSDataSource: Мастер-класс по Dip
RDSDataSource: Мастер-класс по Dip
 
EPAM IT WEEK: AEM & TDD. It's so boring...
EPAM IT WEEK: AEM & TDD. It's so boring...EPAM IT WEEK: AEM & TDD. It's so boring...
EPAM IT WEEK: AEM & TDD. It's so boring...
 
OSGi and Eclipse RCP
OSGi and Eclipse RCPOSGi and Eclipse RCP
OSGi and Eclipse RCP
 
Dependency Injection with CDI in 15 minutes
Dependency Injection with CDI in 15 minutesDependency Injection with CDI in 15 minutes
Dependency Injection with CDI in 15 minutes
 
#11.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원학원,재직자/실업자교육학원,스프링교육,마이바...
#11.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원학원,재직자/실업자교육학원,스프링교육,마이바...#11.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원학원,재직자/실업자교육학원,스프링교육,마이바...
#11.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원학원,재직자/실업자교육학원,스프링교육,마이바...
 
droidparts
droidpartsdroidparts
droidparts
 
Application Frameworks: The new kids on the block
Application Frameworks: The new kids on the blockApplication Frameworks: The new kids on the block
Application Frameworks: The new kids on the block
 
Server1
Server1Server1
Server1
 

En vedette

アップルのテンプレートは有害と考えられる
アップルのテンプレートは有害と考えられるアップルのテンプレートは有害と考えられる
アップルのテンプレートは有害と考えられるBrian Gesiak
 
RSpec 3.0: Under the Covers
RSpec 3.0: Under the CoversRSpec 3.0: Under the Covers
RSpec 3.0: Under the CoversBrian Gesiak
 
Intel® Xeon® Phi Coprocessor High Performance Programming
Intel® Xeon® Phi Coprocessor High Performance ProgrammingIntel® Xeon® Phi Coprocessor High Performance Programming
Intel® Xeon® Phi Coprocessor High Performance ProgrammingBrian Gesiak
 
iOS UI Component API Design
iOS UI Component API DesigniOS UI Component API Design
iOS UI Component API DesignBrian Gesiak
 
iOSビヘイビア駆動開発
iOSビヘイビア駆動開発iOSビヘイビア駆動開発
iOSビヘイビア駆動開発Brian Gesiak
 
iOS UI Component API Design
iOS UI Component API DesigniOS UI Component API Design
iOS UI Component API DesignBrian Gesiak
 

En vedette (6)

アップルのテンプレートは有害と考えられる
アップルのテンプレートは有害と考えられるアップルのテンプレートは有害と考えられる
アップルのテンプレートは有害と考えられる
 
RSpec 3.0: Under the Covers
RSpec 3.0: Under the CoversRSpec 3.0: Under the Covers
RSpec 3.0: Under the Covers
 
Intel® Xeon® Phi Coprocessor High Performance Programming
Intel® Xeon® Phi Coprocessor High Performance ProgrammingIntel® Xeon® Phi Coprocessor High Performance Programming
Intel® Xeon® Phi Coprocessor High Performance Programming
 
iOS UI Component API Design
iOS UI Component API DesigniOS UI Component API Design
iOS UI Component API Design
 
iOSビヘイビア駆動開発
iOSビヘイビア駆動開発iOSビヘイビア駆動開発
iOSビヘイビア駆動開発
 
iOS UI Component API Design
iOS UI Component API DesigniOS UI Component API Design
iOS UI Component API Design
 

Similaire à Apple Templates Considered Harmful

Formacion en movilidad: Conceptos de desarrollo en iOS (III)
Formacion en movilidad: Conceptos de desarrollo en iOS (III) Formacion en movilidad: Conceptos de desarrollo en iOS (III)
Formacion en movilidad: Conceptos de desarrollo en iOS (III) Mobivery
 
Conceitos e prática no desenvolvimento iOS - Mobile Conf 2014
Conceitos e prática no desenvolvimento iOS - Mobile Conf 2014Conceitos e prática no desenvolvimento iOS - Mobile Conf 2014
Conceitos e prática no desenvolvimento iOS - Mobile Conf 2014Fábio Pimentel
 
Bootiful Development with Spring Boot and Angular - Spring I/O 2017
Bootiful Development with Spring Boot and Angular - Spring I/O 2017Bootiful Development with Spring Boot and Angular - Spring I/O 2017
Bootiful Development with Spring Boot and Angular - Spring I/O 2017Matt Raible
 
Using Backbone.js with Drupal 7 and 8
Using Backbone.js with Drupal 7 and 8Using Backbone.js with Drupal 7 and 8
Using Backbone.js with Drupal 7 and 8Ovadiah Myrgorod
 
Mobile App Development: Primi passi con NativeScript e Angular 2
Mobile App Development: Primi passi con NativeScript e Angular 2Mobile App Development: Primi passi con NativeScript e Angular 2
Mobile App Development: Primi passi con NativeScript e Angular 2Filippo Matteo Riggio
 
Your Second iPhone App - Code Listings
Your Second iPhone App - Code ListingsYour Second iPhone App - Code Listings
Your Second iPhone App - Code ListingsVu Tran Lam
 
Hızlı Cocoa Geliştirme (Develop your next cocoa app faster!)
Hızlı Cocoa Geliştirme (Develop your next cocoa app faster!)Hızlı Cocoa Geliştirme (Develop your next cocoa app faster!)
Hızlı Cocoa Geliştirme (Develop your next cocoa app faster!)Sarp Erdag
 
10 tips for a reusable architecture
10 tips for a reusable architecture10 tips for a reusable architecture
10 tips for a reusable architectureJorge Ortiz
 
MVC & SQL_In_1_Hour
MVC & SQL_In_1_HourMVC & SQL_In_1_Hour
MVC & SQL_In_1_HourDilip Patel
 
Developing ASP.NET Applications Using the Model View Controller Pattern
Developing ASP.NET Applications Using the Model View Controller PatternDeveloping ASP.NET Applications Using the Model View Controller Pattern
Developing ASP.NET Applications Using the Model View Controller Patterngoodfriday
 
MVC Puree - Approaches to MVC with Umbraco
MVC Puree - Approaches to MVC with UmbracoMVC Puree - Approaches to MVC with Umbraco
MVC Puree - Approaches to MVC with UmbracoAndy Butland
 
Formacion en movilidad: Conceptos de desarrollo en iOS (IV)
Formacion en movilidad: Conceptos de desarrollo en iOS (IV) Formacion en movilidad: Conceptos de desarrollo en iOS (IV)
Formacion en movilidad: Conceptos de desarrollo en iOS (IV) Mobivery
 
Aurelia Meetup Paris
Aurelia Meetup ParisAurelia Meetup Paris
Aurelia Meetup ParisAhmed Radjdi
 
Using a model view-view model architecture for iOS apps
Using a model view-view model architecture for iOS appsUsing a model view-view model architecture for iOS apps
Using a model view-view model architecture for iOS appsallanh0526
 

Similaire à Apple Templates Considered Harmful (20)

Formacion en movilidad: Conceptos de desarrollo en iOS (III)
Formacion en movilidad: Conceptos de desarrollo en iOS (III) Formacion en movilidad: Conceptos de desarrollo en iOS (III)
Formacion en movilidad: Conceptos de desarrollo en iOS (III)
 
Conceitos e prática no desenvolvimento iOS - Mobile Conf 2014
Conceitos e prática no desenvolvimento iOS - Mobile Conf 2014Conceitos e prática no desenvolvimento iOS - Mobile Conf 2014
Conceitos e prática no desenvolvimento iOS - Mobile Conf 2014
 
Bootiful Development with Spring Boot and Angular - Spring I/O 2017
Bootiful Development with Spring Boot and Angular - Spring I/O 2017Bootiful Development with Spring Boot and Angular - Spring I/O 2017
Bootiful Development with Spring Boot and Angular - Spring I/O 2017
 
201104 iphone navigation-based apps
201104 iphone navigation-based apps201104 iphone navigation-based apps
201104 iphone navigation-based apps
 
iOS
iOSiOS
iOS
 
Backbone js-slides
Backbone js-slidesBackbone js-slides
Backbone js-slides
 
Using Backbone.js with Drupal 7 and 8
Using Backbone.js with Drupal 7 and 8Using Backbone.js with Drupal 7 and 8
Using Backbone.js with Drupal 7 and 8
 
I os 04
I os 04I os 04
I os 04
 
Mobile App Development: Primi passi con NativeScript e Angular 2
Mobile App Development: Primi passi con NativeScript e Angular 2Mobile App Development: Primi passi con NativeScript e Angular 2
Mobile App Development: Primi passi con NativeScript e Angular 2
 
Your Second iPhone App - Code Listings
Your Second iPhone App - Code ListingsYour Second iPhone App - Code Listings
Your Second iPhone App - Code Listings
 
Hızlı Cocoa Geliştirme (Develop your next cocoa app faster!)
Hızlı Cocoa Geliştirme (Develop your next cocoa app faster!)Hızlı Cocoa Geliştirme (Develop your next cocoa app faster!)
Hızlı Cocoa Geliştirme (Develop your next cocoa app faster!)
 
10 tips for a reusable architecture
10 tips for a reusable architecture10 tips for a reusable architecture
10 tips for a reusable architecture
 
MVC & SQL_In_1_Hour
MVC & SQL_In_1_HourMVC & SQL_In_1_Hour
MVC & SQL_In_1_Hour
 
Vue js and Dyploma
Vue js and DyplomaVue js and Dyploma
Vue js and Dyploma
 
Developing ASP.NET Applications Using the Model View Controller Pattern
Developing ASP.NET Applications Using the Model View Controller PatternDeveloping ASP.NET Applications Using the Model View Controller Pattern
Developing ASP.NET Applications Using the Model View Controller Pattern
 
MVC Puree - Approaches to MVC with Umbraco
MVC Puree - Approaches to MVC with UmbracoMVC Puree - Approaches to MVC with Umbraco
MVC Puree - Approaches to MVC with Umbraco
 
Formacion en movilidad: Conceptos de desarrollo en iOS (IV)
Formacion en movilidad: Conceptos de desarrollo en iOS (IV) Formacion en movilidad: Conceptos de desarrollo en iOS (IV)
Formacion en movilidad: Conceptos de desarrollo en iOS (IV)
 
Aurelia Meetup Paris
Aurelia Meetup ParisAurelia Meetup Paris
Aurelia Meetup Paris
 
Using a model view-view model architecture for iOS apps
Using a model view-view model architecture for iOS appsUsing a model view-view model architecture for iOS apps
Using a model view-view model architecture for iOS apps
 
Vue business first
Vue business firstVue business first
Vue business first
 

Apple Templates Considered Harmful

  • 1. Templates Considered Harmful Apple Templates and the Single Responsibility Principle Brian Gesiak March 28th, 2014 at Recruit Co., Ltd. Research Student, The University of Tokyo @modocache
  • 2. Today • The single responsibility principle (SRP) • The UITableViewController file template • Model and controller code all rolled into one • Example of how to separate concerns • Moving data store logic out of the controller in GitHubViewer.app • Apple file and project templates • Many violate the single responsibility principle • Best to think of them as“proof of concepts”
  • 3. Single Responsibility Principle A class should have one, and only one, reason to change. - Robert C. Martin
  • 4. Robert C. Martin (Uncle Bob)
  • 5. UITableViewController #pragma mark - UIViewController - (id)initWithStyle:(UITableViewStyle)style { /* ... */ } - (void)viewDidLoad { /* ... */ } - (void)didReceiveMemoryWarning { /* ... */ } - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { /* ... */ } ! #pragma mark - UITableViewDataSource - (NSInteger)numberOfSectionsInTableView: (UITableView *)tableView { /* ... */ } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { /* ... */ } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ } ! #pragma mark - UITableViewDelegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ } What’s Included in the Template
  • 6. UITableViewController #pragma mark - UIViewController - (id)initWithStyle:(UITableViewStyle)style { /* ... */ } - (void)viewDidLoad { /* ... */ } - (void)didReceiveMemoryWarning { /* ... */ } - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { /* ... */ } ! #pragma mark - UITableViewDataSource - (NSInteger)numberOfSectionsInTableView: (UITableView *)tableView { /* ... */ } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { /* ... */ } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ } ! #pragma mark - UITableViewDelegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ } What’s Included in the Template
  • 7. UITableViewController #pragma mark - UIViewController - (id)initWithStyle:(UITableViewStyle)style { /* ... */ } - (void)viewDidLoad { /* ... */ } - (void)didReceiveMemoryWarning { /* ... */ } - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { /* ... */ } ! #pragma mark - UITableViewDataSource - (NSInteger)numberOfSectionsInTableView: (UITableView *)tableView { /* ... */ } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { /* ... */ } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ } ! #pragma mark - UITableViewDelegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ } What’s Included in the Template
  • 8. UITableViewController #pragma mark - UIViewController - (id)initWithStyle:(UITableViewStyle)style { /* ... */ } - (void)viewDidLoad { /* ... */ } - (void)didReceiveMemoryWarning { /* ... */ } - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { /* ... */ } ! #pragma mark - UITableViewDataSource - (NSInteger)numberOfSectionsInTableView: (UITableView *)tableView { /* ... */ } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { /* ... */ } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ } ! #pragma mark - UITableViewDelegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { /* ... */ } What’s Included in the Template
  • 9. UITableViewDelegate UITableViewDataSource UITableViewController Responsibilities, a.k.a.“Reasons to Change” 1. Determines what data in displayed in the table ! ! 2. Determines how users interact with the table
  • 10. UITableViewDelegate UITableViewDataSource UITableViewController Responsibilities, a.k.a.“Reasons to Change” 1. Determines what data in displayed in the table ! ! 2. Determines how users interact with the table
  • 11. UITableViewDelegate UITableViewDataSource UITableViewController Responsibilities, a.k.a.“Reasons to Change” 1. Determines what data in displayed in the table ! ! 2. Determines how users interact with the table
  • 12. UITableViewDelegate UITableViewDataSource UITableViewController Responsibilities, a.k.a.“Reasons to Change” 1. Determines what data in displayed in the table ! ! 2. Determines how users interact with the table
  • 13. GitHubViewer GitHub icon by @peterhajas, licensed under the Creative Commons License version 3.0.
  • 14. GitHubViewer GitHub icon by @peterhajas, licensed under the Creative Commons License version 3.0.
  • 15. GHVReposViewController - (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; } View Controller Responsibilities
  • 16. GHVReposViewController - (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; } View Controller Responsibilities
  • 17. GHVReposViewController - (void)getRepositories { [self startNetworkIndicators]; [self.apiClient allRepositoriesForUsername:self.username success:^(NSArray *repositories) { [self stopNetworkIndicators]; self.repositories = repositories; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }]; } View Controller Responsibilities
  • 18. GHVReposViewController - (void)getRepositories { [self startNetworkIndicators]; [self.apiClient allRepositoriesForUsername:self.username success:^(NSArray *repositories) { [self stopNetworkIndicators]; self.repositories = repositories; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }]; } View Controller Responsibilities
  • 19. GHVReposViewController - (void)getRepositories { [self startNetworkIndicators]; [self.apiClient allRepositoriesForUsername:self.username success:^(NSArray *repositories) { [self stopNetworkIndicators]; self.repositories = repositories; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }]; } View Controller Responsibilities
  • 20. GHVReposViewController - (void)getRepositories { [self startNetworkIndicators]; [self.apiClient allRepositoriesForUsername:self.username success:^(NSArray *repositories) { [self stopNetworkIndicators]; self.repositories = repositories; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }]; } View Controller Responsibilities
  • 21. GHVReposViewController - (void)getRepositories { [self startNetworkIndicators]; [self.apiClient allRepositoriesForUsername:self.username success:^(NSArray *repositories) { [self stopNetworkIndicators]; self.repositories = repositories; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }]; } View Controller Responsibilities
  • 22. GHVReposViewController - (void)getRepositories { [self startNetworkIndicators]; [self.apiClient allRepositoriesForUsername:self.username success:^(NSArray *repositories) { [self stopNetworkIndicators]; self.repositories = repositories; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }]; } View Controller Responsibilities
  • 23. GHVReposViewController - (void)getRepositories { [self startNetworkIndicators]; [self.apiClient allRepositoriesForUsername:self.username success:^(NSArray *repositories) { [self stopNetworkIndicators]; self.repositories = repositories; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }]; } View Controller Responsibilities
  • 24. GHVReposViewController - (void)getRepositories { [self startNetworkIndicators]; [self.apiClient allRepositoriesForUsername:self.username success:^(NSArray *repositories) { [self stopNetworkIndicators]; self.repositories = repositories; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }]; } View Controller Responsibilities
  • 25. GHVReposViewController - (void)getRepositories { [self startNetworkIndicators]; [self.apiClient allRepositoriesForUsername:self.username success:^(NSArray *repositories) { [self stopNetworkIndicators]; self.repositories = repositories; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }]; } View Controller Responsibilities
  • 26. GHVReposViewController - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } ! - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } ! - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kGHVRepoCellIdentifier]; } ! GHVRepo *repository = self.repositories[indexPath.row]; cell.textLabel.text = repository.name; cell.detailTextLabel.text = repository.repositoryDescription; return cell; } UITableViewDataSource Responsibilities
  • 27. GHVReposViewController - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } ! - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } ! - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kGHVRepoCellIdentifier]; } ! GHVRepo *repository = self.repositories[indexPath.row]; cell.textLabel.text = repository.name; cell.detailTextLabel.text = repository.repositoryDescription; return cell; } UITableViewDataSource Responsibilities
  • 28. GHVReposViewController - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } ! - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } ! - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kGHVRepoCellIdentifier]; } ! GHVRepo *repository = self.repositories[indexPath.row]; cell.textLabel.text = repository.name; cell.detailTextLabel.text = repository.repositoryDescription; return cell; } UITableViewDataSource Responsibilities
  • 29. GHVReposViewController - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } ! - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } ! - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kGHVRepoCellIdentifier]; } ! GHVRepo *repository = self.repositories[indexPath.row]; cell.textLabel.text = repository.name; cell.detailTextLabel.text = repository.repositoryDescription; return cell; } UITableViewDataSource Responsibilities
  • 30. GHVReposViewController - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } ! - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } ! - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kGHVRepoCellIdentifier]; } ! GHVRepo *repository = self.repositories[indexPath.row]; cell.textLabel.text = repository.name; cell.detailTextLabel.text = repository.repositoryDescription; return cell; } UITableViewDataSource Responsibilities
  • 31. GHVReposViewController - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } ! - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } ! - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kGHVRepoCellIdentifier]; } ! GHVRepo *repository = self.repositories[indexPath.row]; cell.textLabel.text = repository.name; cell.detailTextLabel.text = repository.repositoryDescription; return cell; } UITableViewDataSource Responsibilities
  • 32. GHVReposViewController - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } ! - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } ! - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kGHVRepoCellIdentifier]; } ! GHVRepo *repository = self.repositories[indexPath.row]; cell.textLabel.text = repository.name; cell.detailTextLabel.text = repository.repositoryDescription; return cell; } UITableViewDataSource Responsibilities
  • 33. GHVReposViewController - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } ! - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } ! - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kGHVRepoCellIdentifier]; } ! GHVRepo *repository = self.repositories[indexPath.row]; cell.textLabel.text = repository.name; cell.detailTextLabel.text = repository.repositoryDescription; return cell; } UITableViewDataSource Responsibilities
  • 34. GHVReposViewController - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } ! - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } ! - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kGHVRepoCellIdentifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:kGHVRepoCellIdentifier]; } ! GHVRepo *repository = self.repositories[indexPath.row]; cell.textLabel.text = repository.name; cell.detailTextLabel.text = repository.repositoryDescription; return cell; } UITableViewDataSource Responsibilities
  • 35. GHVReposViewController - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { GHVRepo *repository = self.repositories[indexPath.row]; GHVRepoViewController *controller = [[GHVRepoViewController alloc] initWithRepository:repository]; [self.navigationController pushViewController:controller animated:YES]; } UITableViewDelegate Responsibilities
  • 36. GHVReposViewController - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { GHVRepo *repository = self.repositories[indexPath.row]; GHVRepoViewController *controller = [[GHVRepoViewController alloc] initWithRepository:repository]; [self.navigationController pushViewController:controller animated:YES]; } UITableViewDelegate Responsibilities
  • 37. GHVReposViewController - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { GHVRepo *repository = self.repositories[indexPath.row]; GHVRepoViewController *controller = [[GHVRepoViewController alloc] initWithRepository:repository]; [self.navigationController pushViewController:controller animated:YES]; } UITableViewDelegate Responsibilities
  • 38. GHVReposViewController - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { GHVRepo *repository = self.repositories[indexPath.row]; GHVRepoViewController *controller = [[GHVRepoViewController alloc] initWithRepository:repository]; [self.navigationController pushViewController:controller animated:YES]; } UITableViewDelegate Responsibilities
  • 39. GHVReposViewController - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { GHVRepo *repository = self.repositories[indexPath.row]; GHVRepoViewController *controller = [[GHVRepoViewController alloc] initWithRepository:repository]; [self.navigationController pushViewController:controller animated:YES]; } UITableViewDelegate Responsibilities
  • 40. GHVReposViewController - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { GHVRepo *repository = self.repositories[indexPath.row]; GHVRepoViewController *controller = [[GHVRepoViewController alloc] initWithRepository:repository]; [self.navigationController pushViewController:controller animated:YES]; } UITableViewDelegate Responsibilities
  • 41. GHVReposViewController - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { GHVRepo *repository = self.repositories[indexPath.row]; GHVRepoViewController *controller = [[GHVRepoViewController alloc] initWithRepository:repository]; [self.navigationController pushViewController:controller animated:YES]; } UITableViewDelegate Responsibilities
  • 44. GHVReposViewController 1. View controller responsibilities • Kick off repository fetch
  • 45. GHVReposViewController 1. View controller responsibilities • Kick off repository fetch • Show network activity indicators
  • 46. GHVReposViewController 1. View controller responsibilities • Kick off repository fetch • Show network activity indicators 2. UITableViewDataSource responsibilities
  • 47. GHVReposViewController 1. View controller responsibilities • Kick off repository fetch • Show network activity indicators 2. UITableViewDataSource responsibilities • Store repositories
  • 48. GHVReposViewController 1. View controller responsibilities • Kick off repository fetch • Show network activity indicators 2. UITableViewDataSource responsibilities • Store repositories • Specify how many sections in table view
  • 49. GHVReposViewController 1. View controller responsibilities • Kick off repository fetch • Show network activity indicators 2. UITableViewDataSource responsibilities • Store repositories • Specify how many sections in table view • Specify how many rows in each section
  • 50. GHVReposViewController 1. View controller responsibilities • Kick off repository fetch • Show network activity indicators 2. UITableViewDataSource responsibilities • Store repositories • Specify how many sections in table view • Specify how many rows in each section • Create each cell based on repository data
  • 51. GHVReposViewController 1. View controller responsibilities • Kick off repository fetch • Show network activity indicators 2. UITableViewDataSource responsibilities • Store repositories • Specify how many sections in table view • Specify how many rows in each section • Create each cell based on repository data 3. UITableViewDelegate responsibilities
  • 52. GHVReposViewController 1. View controller responsibilities • Kick off repository fetch • Show network activity indicators 2. UITableViewDataSource responsibilities • Store repositories • Specify how many sections in table view • Specify how many rows in each section • Create each cell based on repository data 3. UITableViewDelegate responsibilities • Push view controller for selected repository
  • 53. GHVReposViewController 1. View controller responsibilities • Kick off repository fetch • Show network activity indicators 2. UITableViewDataSource responsibilities • Store repositories • Specify how many sections in table view • Specify how many rows in each section • Create each cell based on repository data 3. UITableViewDelegate responsibilities • Push view controller for selected repository 100+ lines of code
  • 54. GHVReposViewController 1. View controller responsibilities • Kick off repository fetch • Show network activity indicators 2. UITableViewDataSource responsibilities • Store repositories • Specify how many sections in table view • Specify how many rows in each section • Create each cell based on repository data 3. UITableViewDelegate responsibilities • Push view controller for selected repository 100+ lines of code
  • 55. GHVReposViewController What a Bloated View Controller Looks Like
  • 59. GHVRepoStore A UITableViewDataStore and Nothing More - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } ! - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } ! - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /// Dequeue, configure, and return a cell } ! - (void)reloadRepositories:(GHVRepoStoreSuccessBlock)success failure:(GHVRepoStoreFailureBlock)failure { [self.apiClient allRepositoriesForUsername:self.username success:/* ... */ failure:/* ... */]; }
  • 60. GHVRepoStore A UITableViewDataStore and Nothing More - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } ! - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } ! - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /// Dequeue, configure, and return a cell } ! - (void)reloadRepositories:(GHVRepoStoreSuccessBlock)success failure:(GHVRepoStoreFailureBlock)failure { [self.apiClient allRepositoriesForUsername:self.username success:/* ... */ failure:/* ... */]; }
  • 61. GHVRepoStore A UITableViewDataStore and Nothing More - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } ! - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } ! - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /// Dequeue, configure, and return a cell } ! - (void)reloadRepositories:(GHVRepoStoreSuccessBlock)success failure:(GHVRepoStoreFailureBlock)failure { [self.apiClient allRepositoriesForUsername:self.username success:/* ... */ failure:/* ... */]; }
  • 62. GHVRepoStore A UITableViewDataStore and Nothing More - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } ! - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } ! - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /// Dequeue, configure, and return a cell } ! - (void)reloadRepositories:(GHVRepoStoreSuccessBlock)success failure:(GHVRepoStoreFailureBlock)failure { [self.apiClient allRepositoriesForUsername:self.username success:/* ... */ failure:/* ... */]; }
  • 63. GHVRepoStore A UITableViewDataStore and Nothing More - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } ! - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.repositories count]; } ! - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { /// Dequeue, configure, and return a cell } ! - (void)reloadRepositories:(GHVRepoStoreSuccessBlock)success failure:(GHVRepoStoreFailureBlock)failure { [self.apiClient allRepositoriesForUsername:self.username success:/* ... */ failure:/* ... */]; }
  • 64. GHVReposViewController Transferring Responsibilities to the Store - (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } ! - (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; ! ! ! ! ! ! ! ! }
  • 65. GHVReposViewController Transferring Responsibilities to the Store - (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } ! - (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; ! ! ! ! ! ! ! ! }
  • 66. GHVReposViewController Transferring Responsibilities to the Store - (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } ! - (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; ! ! ! ! ! ! ! ! }
  • 67. GHVReposViewController Transferring Responsibilities to the Store - (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } ! - (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; ! ! ! ! ! ! ! ! }
  • 68. GHVReposViewController Transferring Responsibilities to the Store - (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } ! - (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; ! ! ! ! ! ! ! ! }
  • 69. GHVReposViewController Transferring Responsibilities to the Store - (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } ! - (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; ! ! ! ! ! ! ! ! }
  • 70. GHVReposViewController Transferring Responsibilities to the Store - (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } ! - (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; ! ! ! ! ! ! ! ! } self.tableView.dataSource = self.repoStore; [self startNetworkIndicators]; [self.repoStore reloadRepositories:^{ [self stopNetworkIndicators]; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }];
  • 71. GHVReposViewController Transferring Responsibilities to the Store - (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } ! - (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; ! ! ! ! ! ! ! ! } self.tableView.dataSource = self.repoStore; [self startNetworkIndicators]; [self.repoStore reloadRepositories:^{ [self stopNetworkIndicators]; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }];
  • 72. GHVReposViewController Transferring Responsibilities to the Store - (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } ! - (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; ! ! ! ! ! ! ! ! } self.tableView.dataSource = self.repoStore; [self startNetworkIndicators]; [self.repoStore reloadRepositories:^{ [self stopNetworkIndicators]; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }];
  • 73. GHVReposViewController Transferring Responsibilities to the Store - (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } ! - (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; ! ! ! ! ! ! ! ! } self.tableView.dataSource = self.repoStore; [self startNetworkIndicators]; [self.repoStore reloadRepositories:^{ [self stopNetworkIndicators]; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }];
  • 74. GHVReposViewController Transferring Responsibilities to the Store - (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } ! - (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; ! ! ! ! ! ! ! ! } self.tableView.dataSource = self.repoStore; [self startNetworkIndicators]; [self.repoStore reloadRepositories:^{ [self stopNetworkIndicators]; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }];
  • 75. GHVReposViewController Transferring Responsibilities to the Store - (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } ! - (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; ! ! ! ! ! ! ! ! } self.tableView.dataSource = self.repoStore; [self startNetworkIndicators]; [self.repoStore reloadRepositories:^{ [self stopNetworkIndicators]; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }];
  • 76. GHVReposViewController Transferring Responsibilities to the Store - (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } ! - (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; ! ! ! ! ! ! ! ! } self.tableView.dataSource = self.repoStore; [self startNetworkIndicators]; [self.repoStore reloadRepositories:^{ [self stopNetworkIndicators]; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }];
  • 77. GHVReposViewController Transferring Responsibilities to the Store - (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } ! - (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; ! ! ! ! ! ! ! ! } self.tableView.dataSource = self.repoStore; [self startNetworkIndicators]; [self.repoStore reloadRepositories:^{ [self stopNetworkIndicators]; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }];
  • 78. GHVReposViewController Transferring Responsibilities to the Store - (id)initWithRepoStore:(GHVRepoStore *)repoStore { self = [super initWithStyle:UITableViewStylePlain]; if (self) { _repoStore = repoStore; } return self; } ! - (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; ! ! ! ! ! ! ! ! } self.tableView.dataSource = self.repoStore; [self startNetworkIndicators]; [self.repoStore reloadRepositories:^{ [self stopNetworkIndicators]; [self.tableView reloadData]; } failure:^(NSError *error) { [self stopNetworkIndicators]; [UIAlertView showAlertForError:error]; }];
  • 81. GHVReposViewController 1. View controller responsibilities • Connect table view to data store
  • 82. GHVReposViewController 1. View controller responsibilities • Connect table view to data store • Show network activity indicators
  • 83. GHVReposViewController 1. View controller responsibilities • Connect table view to data store • Show network activity indicators 2. UITableViewDelegate responsibilities
  • 84. GHVReposViewController 1. View controller responsibilities • Connect table view to data store • Show network activity indicators 2. UITableViewDelegate responsibilities • Push view controller for selected repository
  • 85. GHVReposViewController 1. View controller responsibilities • Connect table view to data store • Show network activity indicators 2. UITableViewDelegate responsibilities • Push view controller for selected repository Less than 100 lines of code
  • 87. Single Responsibility Principle • Less brittle Benefits
  • 88. Single Responsibility Principle • Less brittle • More modular Benefits
  • 89. Single Responsibility Principle • Less brittle • More modular • Easier to read, understand, and debug Benefits
  • 90. The Problem with Apple Templates If Only They Knew About the SRP
  • 91. The Problem with Apple Templates If Only They Knew About the SRP • Master-Detail Application (with Core Data) • AppDelegate • Sets up UIWindow root view controller • Sets up Core Data stack, handles errors • 100+ lines of code
  • 92. The Problem with Apple Templates If Only They Knew About the SRP • Master-Detail Application (with Core Data) • AppDelegate • Sets up UIWindow root view controller • Sets up Core Data stack, handles errors • 100+ lines of code • OpenGL Application • View controller does it all! • Sets up OpenGL context • Compiles shaders • Stores vertex data • 400+ lines of code
  • 93. Not All Templates Created Equal Some Templates Employ Modular Design
  • 94. Not All Templates Created Equal Some Templates Employ Modular Design • Page-Based Application • Separates concerns among UIPageViewDelegate and UIPageViewDataSource • Small classes
  • 95. Not All Templates Created Equal Some Templates Employ Modular Design • Page-Based Application • Separates concerns among UIPageViewDelegate and UIPageViewDataSource • Small classes • SpriteKit Game • Small classes with a relatively clear separation of concerns
  • 96. Takeaways • Single responsibility principle • Classes should have one, and only one, reason to change • Apple’s templates should be thought of as“proof of concepts”, not as examples of clean, well-structured code • Just because Apple does it doesn’t mean it’s a good idea • Use your better judgement on what’s“clean code”and what isn’t
  • 97. Want More on Clean Code? • Slides for this talk available at http://modocache.io/ apple-templates-considered-harmful • Follow me on Twitter and GitHub at @modocache • Clean Code Resources • Follow Robert C. Martin at @unclebobmartin • More on Object-Oriented Design at http:// www.butunclebob.com/ ArticleS.UncleBob.PrinciplesOfOod • Clean Code videos at http://cleancoders.com/ • Building a Healthy Mistrust of Apple Engineering • Follow Peter Steinberger at @steipete • Follow Justin Spahr-Summers at @ jspahrsummers