Apple Templates Considered Harmful

9,096 views
9,120 views

Published on

0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
9,096
On SlideShare
0
From Embeds
0
Number of Embeds
8,055
Actions
Shares
0
Downloads
16
Comments
0
Likes
2
Embeds 0
No embeds

No notes for slide

Apple Templates Considered Harmful

  1. 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. 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. 3. Single Responsibility Principle A class should have one, and only one, reason to change. - Robert C. Martin
  4. 4. Robert C. Martin (Uncle Bob)
  5. 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. 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. 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. 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. 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. 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. 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. 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. 13. GitHubViewer GitHub icon by @peterhajas, licensed under the Creative Commons License version 3.0.
  14. 14. GitHubViewer GitHub icon by @peterhajas, licensed under the Creative Commons License version 3.0.
  15. 15. GHVReposViewController - (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; } View Controller Responsibilities
  16. 16. GHVReposViewController - (void)viewDidLoad { [super viewDidLoad]; ! self.title = NSLocalizedString(@"Repositories", nil); [self getRepositories]; } View Controller Responsibilities
  17. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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
  42. 42. GHVReposViewController
  43. 43. GHVReposViewController 1. View controller responsibilities
  44. 44. GHVReposViewController 1. View controller responsibilities • Kick off repository fetch
  45. 45. GHVReposViewController 1. View controller responsibilities • Kick off repository fetch • Show network activity indicators
  46. 46. GHVReposViewController 1. View controller responsibilities • Kick off repository fetch • Show network activity indicators 2. UITableViewDataSource responsibilities
  47. 47. GHVReposViewController 1. View controller responsibilities • Kick off repository fetch • Show network activity indicators 2. UITableViewDataSource responsibilities • Store repositories
  48. 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. 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. 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. 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. 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. 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. 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. 55. GHVReposViewController What a Bloated View Controller Looks Like
  56. 56. GHVReposViewController Separation of Concerns
  57. 57. GHVReposViewController Separation of Concerns
  58. 58. GHVReposViewController Separation of Concerns
  59. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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]; }];
  79. 79. GHVReposViewController
  80. 80. GHVReposViewController 1. View controller responsibilities
  81. 81. GHVReposViewController 1. View controller responsibilities • Connect table view to data store
  82. 82. GHVReposViewController 1. View controller responsibilities • Connect table view to data store • Show network activity indicators
  83. 83. GHVReposViewController 1. View controller responsibilities • Connect table view to data store • Show network activity indicators 2. UITableViewDelegate responsibilities
  84. 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. 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
  86. 86. Single Responsibility Principle Benefits
  87. 87. Single Responsibility Principle • Less brittle Benefits
  88. 88. Single Responsibility Principle • Less brittle • More modular Benefits
  89. 89. Single Responsibility Principle • Less brittle • More modular • Easier to read, understand, and debug Benefits
  90. 90. The Problem with Apple Templates If Only They Knew About the SRP
  91. 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. 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. 93. Not All Templates Created Equal Some Templates Employ Modular Design
  94. 94. Not All Templates Created Equal Some Templates Employ Modular Design • Page-Based Application • Separates concerns among UIPageViewDelegate and UIPageViewDataSource • Small classes
  95. 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. 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. 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

×