The document discusses best practices for iOS development using Xcode, including organizing code into groups and folders based on separation of concerns, implementing isolated and standalone data sources and views, and writing tests for components in isolation to avoid dependencies on other parts of the app. It provides an example of a category test for parsing timestamps, implementing and testing a standalone data source class, and exercises for further improving testing isolation.
Tata AIG General Insurance Company - Insurer Innovation Award 2024
iOS Development Methodology
1. iOS Development
Methodology
Applying Best Practices to the Xcode World
Thursday, July 25, 13
This is a “Guided Exploration”, though I will be talking a lot.
Please feel free to ask questions at any time, I’d like this to be more of a discussion than a
presentation.
the Ruby community has been preaching test-first Agile methodologies for years, even if you don’t entirely buy into what they are
saying, they are pushing
the discussion forward and improving their best practices. Today I want to talk about applying some of these practices to iOS/
Objective-C development in Xcode.
3. What you’re in for
• Xcode’s bad influences
• Organization and Separation of Concerns
• Testing, and in Isolation
Code: https://github.com/smartlogic/ios-best-practices
or http://goo.gl/skgRY
Thursday, July 25, 13
This is what we’re going to talk about
This talk is aimed at beginner to intermediate developers (but hopefully something for
everyone)
Some familiarity with the structure of iOS apps is somewhat necessary.
5. Xcode’s Bad Influences
• Lack of Project Structure
• Questionable Templates
Thursday, July 25, 13
Two primary gripes.
Filesystem does not match project organization
Lousy for any non-xcode view of the project (github/bitbucket)
6. Solutions
• Create Folders for Groups
• Strip Comments
• Avoid UITableViewController
Thursday, July 25, 13
First and foremost you can actually create folders for groups.
Ensures files added to that group will default to that folder.
UItableViewController adds unecessary noise (more on that later)
File comments are redundant in the world of git.
7. Better Templates
github.com/mneorr/Alcatraz/
Alcatraz - Xcode Plugin Manager
Thursday, July 25, 13
Use Alcatraz.
We have templates that should show up there soon.
Starting with an empty Xcode project is good practice.
It is important to know all the steps required to get to that Master-Detail view example
8. Better Templates
github.com/mneorr/Alcatraz/
Alcatraz - Xcode Plugin Manager
(or build from scratch, it’s good practice)
Thursday, July 25, 13
Use Alcatraz.
We have templates that should show up there soon.
Starting with an empty Xcode project is good practice.
It is important to know all the steps required to get to that Master-Detail view example
10. What are Concerns?
• Roles within the Application
• View Controllers
• Views
• Services
• Models
• Delegates (protocol implementations)
Thursday, July 25, 13
Concerns are the different components that make up your application.
Controllers generally coordinate other objects (models, views, services and delegates)
The other objects should essentially worry about themselves.
11. How to Separate?
• Organize into groups/folders
• Keep as isolated as possible
• Stand-alone protocol implementations
Thursday, July 25, 13
Organization is important, just put each in it’s own place
Isolation may take some practice, let’s touch on that real quick.
12. Data Source Isolation
Thursday, July 25, 13
A DataSource can be extremely simple.
Expose NSMutableArray “items” to be setup externally
Put Cell configuration code in the cell.
13. Data Source Isolation
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
WidgetCell *cell = [tableView dequeueReusableCellWithIdentifier:WidgetCellIdentifier
forIndexPath:indexPath];
[cell configure:[self.items objectAtIndex:indexPath.row]];
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.items count];
}
Thursday, July 25, 13
A DataSource can be extremely simple.
Expose NSMutableArray “items” to be setup externally
Put Cell configuration code in the cell.
14. DataSource Isolation
- (void)viewDidLoad
{
// (minor setup code)
// WidgetCellIdentifier declared in data source header
[self.tableView registerClass:[WidgetCell class]
forCellReuseIdentifier:WidgetCellIdentifier];
self.dataSource = [[WidgetsTableDataSource alloc] init];
self.tableView.dataSource = self.dataSource;
[WidgetAPIService getWidgets:^(NSMutableArray *widgets) {
self.dataSource.items = widgets;
[self.tableView reloadData];
}];
}
Thursday, July 25, 13
The entire UIViewController is basically the viewDidLoad method.
Our service loads items based on an API call.
This gives us a nicely isolated data source.
This is a good lead in to testing in isolation
15. Testing, and in Isolation
Thursday, July 25, 13
Testing in isolation means testing individual components without relying on external
components.
If you need to be sold on testing, in short it allows you to change and refactor your code and
knowing that doing so doesn’t break anything (or finding it and fixing it).
It reduces the
16. A Note on Tools
• BDD Tool: Kiwi
• Web Mocking: Nocilla
• (Ditch whatever Xcode gives you)
Thursday, July 25, 13
Hitting the network for every test is just a bad idea.
I like to test against a dev server until tests are greeen, then stub requests.
17. A Category Test
Testing the parsing of a timestamp string to an
NSDate
Thursday, July 25, 13
This is a good first test. It is isolated by nature (being a category).
This is a legitimate test, whenever you are parsing a string to turn it into something else you
should
test that your parsing works.
18. A Category Test
describe(@"NSDate+Timestamps", ^{
it(@"Initializes an NSDate from a timestamp string", ^{
NSString *timestamp = @"2010-02-03T04:05:06Z";
NSDate *date = [NSDate dateWithTimestamp:timestamp];
NSUInteger units = NSDayCalendarUnit|NSMonthCalendarUnit|NSYearCalendarUnit|
NSHourCalendarUnit|NSMinuteCalendarUnit|NSSecondCalendarUnit;
NSDateComponents *components = [[NSCalendar currentCalendar]
components:units
fromDate:date];
[[theValue(components.year) should] equal:theValue(2010)];
[[theValue(components.month) should] equal:theValue(2)];
[[theValue(components.day) should] equal:theValue(3)];
[[theValue(components.hour) should] equal:theValue(4)];
[[theValue(components.minute) should] equal:theValue(5)];
[[theValue(components.second) should] equal:theValue(6)];
});
});
Thursday, July 25, 13
Our test. Give it a timestamp string, expect a proper NSDate back.
22. Testing a Data Source
Thursday, July 25, 13
This is where we test our stand alone data source.
By splitting out the class, we can now test it without involving the view controller (and any
weight that may carry)
23. Testing a Data Source
describe(@"WidgetsTableDataSource", ^{
it(@"Returns WidgetCell configured with correct Widget", ^{
UITableView *tableView = [[UITableView alloc] init];
WidgetsTableDataSource *dataSource = [[WidgetsTableDataSource alloc] init];
Widget *widgetOne =[[Widget alloc] init];
widgetOne.name = @"First Widget";
Widget *widgetTwo =[[Widget alloc] init];
widgetTwo.name = @"Second Widget";
dataSource.items = [@[widgetOne, widgetTwo] mutableCopy];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
UITableViewCell *widgetCell = [tableView cellForRowAtIndexPath:indexPath];
[[((WidgetCell *)widgetCell).nameLabel.text should] equal:@"First Widget"];
});
});
Thursday, July 25, 13
Step through.
We initialize a table view (data source methods require it)
We initialize our data source
We add two widgets to the data source items.
We verify that cellForRowAtIndexPath returns the correct cell data.
Note that this somewhat inadvertently tests WidgetCell, but that is not our concern so much
as the verification that the right cell is returned.
Why test something this simple? Well, they say that the two hardest things in computer
science are cache invalidation, naming things and off-by-one errors.
Besides which, your items array could be a more complex collection.
24. Testing a Data Source
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
WidgetCell *cell = [tableView dequeueReusableCellWithIdentifier:WidgetCellIdentifier
forIndexPath:indexPath];
[cell configure:[self.items objectAtIndex:indexPath.row]];
return cell;
}
Thursday, July 25, 13
Once again, the Data Source implementation.
Very simple.
25. Exercises for the Reader
• Test WidgetCell configuration
• Isolate the Data Source Test (don’t rely on widget cell)
Thursday, July 25, 13
There are improvements that could be made here, if interested.