Image courtesy of unsplash

2020 -Yummypets iOS App Stack

Sacha Durand Saint Omer
Yummypets Developers
6 min readDec 29, 2016

--

We had previously published this piece and updated it for 2020. This article will walk you through the tools and architecture techniques we use to power our massive social network App for pet lovers : Yummypets.

Let’s get to it!

Code

100% of the App is written in Swift, there is a total of 54k lines of code across 759 source code files.

MVC

The whole app is following the good’ol MVC Pattern. When controllers get too big, we refactor them into smaller controllers. There is no VIPER, VIP, MVVM, Redux and the like. In order to decouple the view controllers between them, we use “Flow” controllers. also known as the Coordinator pattern or Navigators. As their name suggests, they are top-level controllers which sole responsibility is to handle the navigation between child controllers. We have a Flow controller for each big fetaure of the app, for example SignUpFlow, MagazineFlow, MailboxFlow etc.

Core

All of our models and business logic are encapsulated in a Core.framework which is a separate Swift.package used locally. This forces us to keep things well separated between logic and view code. The core has NO external dependency, it only uses a local Combine helper that gives the Combine api a more promis-y look. The core package has its own set of unit test suite that tests the pure Model logic and mocks all asynchronous calls.

Network Layer

Our Network layer is “injected” at runtime. The way we do dependency injection is quite novel in the sense that instead of using interfaces, our Core Models declare overridable endpoints via static enums in extensions. On App start, we set the endpoints with their concrete (http) implementation, for example : `YPArticle.Endpoints.fetch = api.fetch`.

This has many advantages. Firstly the entire Core and App are http and JSON agnostic. Then since we “inject“ the use-cases one by one it is very easy to mock them independently, which is a great plus when unit testing.

Since this technique relies on runtime, we have a function that verifies all the application endpoints are set on App start. This is a reasonable tradeoff given the advantages listed above.

Image Loading and Cache 🖼

We use Kingfisher for async loading of our images and cache handling. The api is simple yet very customizable. We were previously using AlamofireImage but switched to Kingfisher for performance reasons.

Networking ☁️

For connecting to our JSON api, we use Networking ⚡️ which is a lightweight Combine-based JSON networking library. It adapts nicely with Arrow, our JSON parsing library. This neat layer of abstraction enables us to add and maintain routes efficiently.

Asynchronous code 🕐

We used to rely heavily on Promises to keep our async code clean and maintainable with Then 🎬. Now that Combine is out for iOS13, the latest version of our App uses Combine for all the asynchronous tasks. Native is the way forward and we should always strive for less dependencies, even if that means ditching our own tools.

JSON Parsing ⚙️

We had strong requirements for our JSON parsing library: It should not force us to subclass our Models, support both classes and structs, and be as simple as possible. For that we use Arrow 🏹, it takes care of all the boilerplate JSON parsing code for us and keep model mappings clean and maintainable.

Layout 🏝

We do not use Storyboards nor Xibs for reasons that we will no elaborate here. TLDR: It’s easier to maintain. You can find a great article on that matter here.

Yes, all of our views and TableView Cells are in code ! And this brought up great benefits!

As you may know though, pure Autolayout code is quite verbose and that’s why we use Stevia🍃 for keeping it readable and maintainable.

Now that SwiftUI is out we might start migrating slowly some parts of the App but a full migration would be too costly, given that things work perfectly the way they are at the moment :)

“Shy” Navigation Bar 🙈

For an immersive feed with the navigation bar hiding on scroll we use andreamazz’s AMScrollingNavbar implementation.

Image Picker & Filters 📸

Concerning media capture, we had quite a few requirements.

We wanted a solution that is full Swift, supports photo, video, library, square crop and filters! Unsurprisingly we had to write our own. For that, we rolled out our own version of an heavily inspiredInstagram design : YPImagePicker.

Deployment 🚀

Continuous Integration 🤖

Bitrise is such a pleasure to work with. Making a new build went from 2 hours to literally a few minutes! We use it to run unit tests everyday. And pushing on “staging” branch automatically triggers a TestFlight build. Less time making builds = more time coding \o/. Plus it has all the benefits of continuous integration, such as making sure the app builds on a fresh machine.

TestFlight ✈️

We use Testfilght for sharing builds with our internal and beta testers. Yes there are tons of alternatives out there but most of the time, we try to be good Apple citizens and use the Apple tool. The most annoying thing about Testflight is that a build can sometimes hang between half an hour and 4 hours before being delivered to testers with no apparent reason. The awesome part is that it’s fully integrated with iTunes connect and keeps things simple.

Git flow 🐙

Our git flow is pretty standard we develop features on a feature branch say “filters” then merge it on “dev”. Once finished we test the results by merging on “staging”, which automatically triggers the TestFlight deployment. Once approved by Apple, we merge “staging” into “master”.

Tools 🛠

Dependency Management 🕸

Swift Package Manager is our dependency manager of choice. Once again going with the native route is a no-brainer for us. We are still in a period of transition so not all dependencies support SPM yet. Considering this, our rule of thumb is : use every dependency we can via SPM. If they are not available via SPM, use Carthage which is simple and doesn’t get in the way. For libraries only available through Cocoapods, we prefer installing them manually. We think Carthage is simple, while Cocoapods is easy.

Swift package manager doesn’t support resources (yet !) so most of the non-SPM libraries tend to be UI related ones.

Code linting

We use SwiftLint to keep our codebase clean and consistent.

Localisation 🏁

We use Localize, It keeps our localization files clean and sorted alphabetically. It checks for missing, untranslated and unused keys thus allows for a stress-free localization of the App.

We use Laurine to generate type-safe structs from our Localization.string files. Using a dot syntax like “UserValidation.Popup.Title” now generates a nice namespaced struct. On top of the sweet autocompletion, the compiler now breaks if a translation key is missing!

Assets safety net 👮

In an world where design trends change every year, we needed a way to guard against missing assets. While solutions like Misen or SwiftGen are great, they generate extra swift structs and moves us aways from Apple solution aka image literals. They solve one issue: code breaks when asset disappears, because the corresponding struct does as well.

That’s exactly what AssetChecker👮 does while enabling us to use the great and very readable image literals :) Plus it warns when an Asset is not used in the code, great for cleaning a long lasting project.

Push Notifications 📲

Coming from Parse Push (RIP) we looked carefully for an alternative solution. We chose Batch and are very happy with it. Their UI is super simple and their segmentation tool oh so powerful. Automatic campaigns are also fantastic for combatting retention \o/

AB Testing ⚗️

Optimizely enables us to test out ideas in a data driven way.

Crash Reporting 🐞

We use Crashlytics which is now part of Firebase.

Analytics 📈

Google analytics is our monster for analytics and we but we tend to use Fabric more and more since it delivers a bite-sized analytics that covers most of the basic needs, DNU, DAU, MAU etc.

Deep Linking 🔗

Detecting where someone comes from even before they actually install the App is pretty Magic. For that we use Branch solution.

And that’s pretty much it! I hope you enjoyed this piece and learned about a big social network app architecture and tooling.

If you have any questions feel free to reach out on Twitter :)

More goodness ❤️

If you liked this article make sure to clap 👏 and share, this means a lot to us :)

--

--

Sacha Durand Saint Omer
Yummypets Developers

Avid learner, iOS Engineer @Yummypets, ❤ Swift, Health, Languages & Music