Open Sourcing Denver Rail iOS
Denver Rail was conceived in 2008, shortly after the announcement that the iPhone OS SDK (as it was then known) would be opened up to external developers. The problems the application is designed to solve are:
- When are the next trains coming at the nearest station?
- When are the next trains coming at at future time at a particular station?
The use cases associated with these questions are:
- How long do I have until I need to be at the station to catch a train?
- What time should I plan to be at the station to catch a train?
Most of the existing solutions answer the question of which train to take, which daily commuters already tend to know.
The application itself was built when the iPhone SDK was very immature. Documentation in 2008 was also very poor, and there was very little help from Google (in part due to an NDA placed on the SDK). One silver lining of this was that there was exactly one screen size and resolution to develop for, and no legacy SDKs or hardware to contend with.
Why Open Source
Denver Rail is a popular application in the local market, and we’ve decided to make the codebase available for a few reasons:
It’s very popular locally; we want to provide something useful to the community. It is a simple but non-trivial application for those learning iOS or hobbyists to learn from and contribute to. It gives folks who are interested in working with us (both as developers and clients) a window into something we’ve built. With the community’s help, the application will be easier to keep up to date with new time tables that are released a couple of times a year and new stations that are built.
We’ve elected to use the MIT license, so feel free to use the code in your own projects as well. We do ask that to avoid confusion contributions be added to the “official” Tack Mobile repo so we can make sure this gets submitted to the app store as one consistent application.
Updating older iOS code bases
As developers it’s easy to be excited about fun new technologies that seem like they will work better and make our lives easier, and it’s almost always a requirement that we stay up to date on these things. However it’s not rare that you’ll come across a code base that is older and may need some updates. It’s important to be able to understand the changes that have happened over the iterations of new operating systems and language updates. When coming onto a legacy code base it’s easy to get carried away with small style guide changes that are just personal preference, such as spacing, deleting code that has been commented out for years, and deleting
NSLog’s. In this article we’ll focus on the changes made that are considered ‘best practice’ by Apple and across the industry, and dig a little bit into the reasons behind these changes.
With the release of iOS 6, the llvm compiler added auto synthesis to Objective-C properties. This is a compile time generation, creating standard getters, setters, and instance variables for your properties. In Denver Rail, there still were many properties declared using
@synthesize, from pre iOS 6 days. This is still a valid way to declare properties but can cause some confusion to developers new to the project, or new to iOS. Because of auto synthesis happening behind the scenes and across all classes now it’s generally considered best practice to allow the compiler to do its thing unless you have a specific reason that you do not want your properties synthesized or if you want your instance variable to follow different naming conventions. It is worth the time to update many classes to the commonly accepted @property pattern to keep the code standard and readable to most people. This refactor was made easier by the work being done to move property declarations from the header, or .h files into the implementation or .m files, as it is best practice to keep data encapsulated to the files where it is used, instead of exposing everything unnecessarily.
On a related note, where making the above changes we also updated to use dot syntax for accessing property values in many places. This can also be considered a personal preference but many believe Apple is actively encouraging iOS developers to move to using dot syntax as they update their internal classes to add support for this. Additionally, It is more readable to see
self.aTextField.text.length compared to
[[[self aTextField] text] length]; Although it compiles down to the same message sending that Objective-C uses either way, it is just a more modern, readable, and concise way of accessing properties on objects, and lends itself to Swift interoperability best.
Similar to keeping properties private in the .h file vs the .m file you also want to privatize your
#import’s where possible. Often times you may have a property on your class that needs to be public that is a custom class or you might otherwise think needs an
#import in the header file to keep the compiler from warning “Unknown type name”. However it’s generally best practice to just use
@class ClassName; which just tells the compiler that the class exists without importing the whole thing. This avoids potentially slowing down compile time because if another class imports your class with the unnecessary import, they’re also importing THAT class and everything IT imports. Which is unnecessary and also can cause importing loops. The exceptions are Foundation or when you need the superclass from which your class inherits, or when your class conforms to a protocol, where it needs to know everything about the protocol, not just that it exists.
Another change was to start centralizing all user facing strings into a localizable file. It’s nice to have all user facing strings in one place for being able to double check spelling and to easily be able to update copy. Especially as an app grows and you can get different developers coming in and out of a project, being able to easily find a specific string is useful. While Denver Rail is obviously a Denver specific app, it was worth it to take the time to make user facing strings localized as well while moving them into a strings file. We won’t be trying to expand the app into international markets, obviously, but there is a large Spanish speaking population in Denver and we hope to localize the app into Spanish so that if your phone has Spanish set as the OS language, it will automatically use Spanish in Denver Rail. Although this is not often a priority for startups trying to just get an MVP (Minimum Viable Product) out there, it’s a best practice that can benefit you, as well as future developers on the project, to centralize where your strings live. It makes things easier for you to find as well as making localization an easier task for future iterations.
This idea of centralizing related objects also applies to the constants file added for non user facing string constants. Denver Rail uses
NSUserDefaults for saving some user settings and sharing location permission information across classes. These both use strings for notification names and for key names. You have to write the notification name in the class sending the notification as well as wherever in your app you might need to listen for that notification. It’s all too easy to make a spelling mistake when typing these strings when there is no auto complete. And if you do make a mistake you can waste a lot of time trying to debug why your app is having strange behavior from missing a notification. To avoid this you can centralized all
NSNotification names into a
DRNotificationName struct. This allows for autocomplete when typing your notification names and makes it so that you only type the actual string constant once, cutting down on chances for typos and errors. You can use the same type of struct for user default keys which can also be accessed across the app.
For constants that are private to certain classes, such as UITableViewCell Identifiers, CGFloat constants, and other string and non-string constants, we did some more modernization. For example we replaced
#define kSomeConstant = 50.0
static CGFloat const kSomeConstant = 50.0f
#define works just fine, you do get a little more with the second type of declaration.
#define is a preprocessor directive which means it replaces all occurrences of
kSomeFloatConstant with your value, in this case 50.0. This is the behavior you want, but as you can see by the definition, there is no type declared, and no way to know what kind of object
kSomeConstant might be. When declared as a static
CGFloat you get extra type checking and warnings if you try to use it in a way that
CGFloat cannot handle. The
const keyword is also useful as it is explicitly telling the compiler that the value should not change and will throw an error if you or a future developer try to change it elsewhere in code. These kind of things may come across as nitpicky changes when you know that
#define will work, but knowing the benefits behind the second way of doing things makes it all make a little more sense.
We would love to see people in the Denver iOS community contribute to Denver Rail! The link to the Github repository is below. We have a few open ‘issues’ if you’re not sure where to start and we’re definitely open to suggestions and people opening their own issues and features/enhancements for the project.