In this talk, Mo Kudeki shares lessons learned over the past year picking up Swift while taking the Hey! VINA app from MVP to production with thousands of daily users.
Introduction
My name is Mo Kudeki, and I’m going to tell you about my experiences with Swift as a newcomer, including the lessons I learned over the last year.
The Start of VINA
My story with VINA begins in early 2016 - a year ago. I knew nothing about Swift then, and my friend Olivia and her co-founder had just built a Minimum Viable Product of her app in six months.
The idea behind the app is to make a kind of Tinder for female friendships. The main features of the MVP were present, though there were bugs, I was impressed given that it was completed in six months, and that it was the co-founder’s first app.
At the early stages of a startup, a lot of things will be built from scratch, and your team will likely be small. Each of us had a core area of expertise, but we had to learn a lot of new things. For me, Swift was one of those things I had to learn.
How VINA Was Built
The co-founder was a full-stack engineer with limited mobile experience. To save time, the MVP was built on Parse, which is a mobile backend as a service owned by Facebook.
One of the first things I had to fix was the push notifications on the app - they weren’t working with Parse. Because Swift was concise and readable, going through the new code base was easy. All the classes and functions were public by default, this made it easy to add a fix, or a new library to handle a feature.
To fix the push service, I added a new CocoaPod and proceeded to the next issue. The whole experience took me a couple of hours to fix.
Release
Upon release, we got 100,000 installs in the first two weeks. The product was good, but we did make some mistakes by not preparing for the demand.
One mistake was an upper bound to the number of requests per minute that Parse allowed, which couldn’t simply be fixed by paying them more money. As a solution, we implemented a waitlist system after release. We locked people out of the app, and let them in one by one to limit that number of requests.
As a result of this fix, we got a lot of bad reviews. Moreover, because of the amount of mainstream press, the app was exposed to a lot of users who aren’t used to using beta products - which resulted in many frustrated users.
Unfortunately, bug reports were largely not helpful as many users left us vague information like, “Something’s glitchy”. To tackle this problem, we started a Slack channel for enthusiastic users, so we can push a TestFlight build directly to them, and get immediate feedback.
Example
In one case, we weren’t able to reproduce a crash from the Slack channel, so we invited the local user to our office (which was an apartment).
At the top of her card stack, she was viewing a user without a first name (this is uncommon, as most people have first names). This is how we’re displaying that name:
let userName =
user["firstName"] as! String
You can see the forced unwrapped optional here. If the user’s first name is nil, we will crash. We fixed this with just a more appropriate use of optionals, making sure the name is no longer nil, thus avoiding a crash.
if let userName =
user.objectForKey("firstName") {
displayNameLabel.text =
userName.uppercaseString
}
A note about this fix. I did not know about guard
statements at the time. After this experience, I learned my lesson and cleaned up a lot of the unwrapped optionals in our code. I also learned about the default syntax for the switch
statement, which doesn’t do the fall-through, eliminating some really common bugs.
The Learning Curve
While converting things to proper types, I began to see a lot of basic Swift features in action and gain an appreciation for how Swift makes it more difficult to make mistakes.
An example are Extensions
, which we used to simplify the code base. To illustrate this, we reduced repeating code used to round a profile image by writing an extension of UIView that can apply this rounding.
With each feature delivery, we learned a new Swift feature, thus refining our app and reducing the tech debt. In my opinion, it’s not cost-effective to write “perfect code” at an early stage.
Changing the Backend
On the same day we launched, Parse announced they were shutting down their entire service. Because our app involves a lot of user to user interaction, we had to build a new backend, and we had to do all the migration of users at the same time to ensure that the data is in sync.
To complete the migration, we planned for a maintenance period of a few hours, and a new version of the app needed to be available on the App Store at the same time.
Three things needed to occur for everything to be successful:
- The app had to be able to connect to both Parse and the new backend.
- It had to be able to start out on the old system and make a one-way migration to the new one.
- It had to lock the user out during the downtime window.
We had some challenges to this change - we had no API specific area of our code, as Parse handled everything, and the code to interact with Parse was spread throughout the app. Slowly, the app began to morph into a giant if
else
block, resulting in ugly code while trying to write to both databases.
A solution we considered was to create different versions of a function to handle the different setup logic or structured data. But this was difficult to detangle.
A solution which was the most satisfying for me, as an engineer, was to separate the logic and build a protocol to handle the model objects. We create a protocol that allows you to display a location and the user with a location from Facebook.
protocol LocationDisplayModel {
// What we need to display the user's location in a view
var neighborhood: String? { get set }
var facebookLocation: String? { get set }
var currentLocation: String? { get }
var showNeighborhood: Bool { get set }
...
func toggleUseNeighborhood()
}
It might have a neighborhood, and flags for whether we show it. We don’t care about what the data source is, and we make sure the old and the new type of location satisfies the protocol.
This third strategy takes the most time but is worth it. It’s also good because it documents the intent of your code, as protocols often do.
Questions
Did you consider moving to another service like Parse rather than making your own backend?
We could have, but we felt a little bit burned by what had happened to us when Parse shut down, and we wanted to make something where we felt we were in full control.
At the time the co-founder / CTO was very familiar with the Rails backend, so she wanted to move to that. It was something we could do quickly, and we knew we could do it effectively as well.
Receive news and updates from Realm straight to your inbox