Transcript

Templates Considered HarmfulApple 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

UITableViewControllerResponsibilities, 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

UITableViewControllerResponsibilities, 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

UITableViewControllerResponsibilities, 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

UITableViewControllerResponsibilities, 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

GHVReposViewControllerWhat a Bloated View Controller Looks Like

GHVReposViewControllerSeparation of Concerns

GHVReposViewControllerSeparation of Concerns

GHVReposViewControllerSeparation of Concerns

GHVRepoStoreA 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:/* ... */]; }

GHVRepoStoreA 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:/* ... */]; }

GHVRepoStoreA 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:/* ... */]; }

GHVRepoStoreA 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:/* ... */]; }

GHVRepoStoreA 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:/* ... */]; }

GHVReposViewControllerTransferring 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]; !!!!!!!!}

GHVReposViewControllerTransferring 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]; !!!!!!!!}

GHVReposViewControllerTransferring 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]; !!!!!!!!}

GHVReposViewControllerTransferring 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]; !!!!!!!!}

GHVReposViewControllerTransferring 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]; !!!!!!!!}

GHVReposViewControllerTransferring 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]; !!!!!!!!}

GHVReposViewControllerTransferring 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]; }];

GHVReposViewControllerTransferring 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]; }];

GHVReposViewControllerTransferring 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]; }];

GHVReposViewControllerTransferring 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]; }];

GHVReposViewControllerTransferring 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]; }];

GHVReposViewControllerTransferring 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]; }];

GHVReposViewControllerTransferring 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]; }];

GHVReposViewControllerTransferring 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]; }];

GHVReposViewControllerTransferring 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 PrincipleBenefits

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 TemplatesIf Only They Knew About the SRP

The Problem with Apple TemplatesIf 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 TemplatesIf 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 EqualSome Templates Employ Modular Design

Not All Templates Created EqualSome Templates Employ Modular Design

• Page-Based Application • Separates concerns among UIPageViewDelegate and UIPageViewDataSource

• Small classes

Not All Templates Created EqualSome 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

top related