Contenu connexe Similaire à Apple Templates Considered Harmful Similaire à Apple Templates Considered Harmful (20) Apple Templates Considered Harmful1. 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”
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
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
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
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];
}];
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
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
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