 The iOS Framework made with love @Streamroot

Lamine Ndiaye
Lumen Engineering Blog
6 min readSep 12, 2018

At Streamroot, our goal is to be compatible with 100% of broadcasters’ traffic. One of our efforts in striving towards this goal is in developing our product for multiple devices and platforms, including the Apple ecosystem.

For those of you who are unfamiliar with our product, Streamroot DNA™ harnesses the power of WebRTC and distributed networks to overcome the challenge of delivering high-quality OTT video at scale. By intelligently multi-sourcing content delivery, Streamroot DNA™ offers flexible reliability with a solution that scales naturally to any platform and delivers high quality to any audience. For more info check out our website.

Building an iOS framework involves not only setting up a great architecture but also a high-quality product. This article covers the daily challenges we face at Streamroot to build our iOS Framework, and what we do to solve them: embedding a complex technical stack in the SDK, keeping up with the Apple environment evolution and dependencies and maintaining a clean API.

Our polyglot technical stack (and how we handle it)

Streamroot has a rich technical stack (as you can discover on our StackShare), including our backend and data pipeline. It also includes our cross-platform core technologies that make our product available on Android, iOS, and Web, written in JavaScript and shared between different platforms. To unify implementation and avoid duplicated work on the native side, we decided to share code on both iOS and Android. This code is written in C++ which has a fair interoperability with Objective-C; we then use Djinni to generate cross-language type declarations and interface bindings between C++, iOS and Android.

Another tech stack complexity we need to resolve is embedding WebRTC in our SDKs. As mentioned earlier, our technology creates a mesh network of devices, relying on WebRTC — a free, open-source project, developed by Google, that provides web browsers and mobile applications with real-time communication via a simple application. Our biggest challenge here was building WebRTC for tvOS, which is not provided by default. We forked the repo and made a custom build to comply with the tvOS API.

Finally, using Swift/Objective-C interoperability can have consequences on the API of the framework. In fact, to use Objective-C code from a swift class, bridging-headers must be added to the project. In a Cocoa Framework, this method is not allowed, and the common solution is to put all needed Objective-C headers as public, which can be confusing to the end customer. So we used a module map to define the private headers, so they will appear in a distinct folder as a private APIs. Here is an example of the Modulemap structure:

Modulemap file example

An apple for every season: keeping up with the Apple environment changes and handling dependencies

On top of embedding some Javascript, and integrating some C++ with a complex WebRTC library, one of our biggest challenges is to keep up with Swift evolution.

Swift is the new programming language mainly used to build iOS, macOS, watchOS and tvOS applications. The implementation started by Apple in 2010, was presented at the WWDC in 2014 and has been open-sourced by 2015. Since the first version, the language is evolving pretty fast and now even more with community contributions: only four years after it was presented, Swift already has 13 versions.

We provide a precompiled framework, so we need to make sure to be compatible with the last version of Swift, that involves updating the toolchain (Carthage, Cocoapods, Fastlane, .swiftversion) with each new Swift version release. As not all customers migrate right away to the latest XCode/Swift versions, multiple versions need to be maintained at the same time

to face the Swift evolution. Continuous Integration is a great help to update all our tools without breaking the delivery process. We created a checklist to ensure every release is done properly without missing anything:

  1. Update technical Changelog
  2. Update the customer Changelog
  3. Plist version bump
  4. Carthage and Cocoapods deployments
  5. Tag release creation
  6. Cocoapods Podspec push
  7. Test SDK validation on the AppStore
  8. Internal documentation update: iOS
  9. Merge Git Master branch into develop branch
  10. Update reference project
  11. Generate Jazzy Documentation
  12. Public documentation update

As you may see there are several tasks, some very small, but all of them are crucial and can be automated. The best way to avoid mistakes is to have the CI handle them. For that, we use Fastlane which basically allow us to handle the whole delivery process with premade command lines.

Keep in mind that in order to properly keep up with Swift evolution, dependencies must be handled correctly. Having to handle dependencies from a framework has been an internal challenge in the Apple environment. As Apple forbids embedding a framework in another one, the most common solution is the use of a dependencies manager to resolve all the dependencies for the customers: the common ones are Carthage and Cocoapods.

Distributing the framework with Cocoapods is pretty straightforward; all the dependencies are handled in the Podspec, which is a file describing all the library specifications. However, it can be a little trickier with Carthage. In general, frameworks distributed via Carthage are stored on a git repo with a `cartfile.resolved` file defining all external dependencies. In the case of a pre-built framework stored in a zip, there is no way to set the `cartfile.resolved`, resulting in unsolved dependencies.

There are different workarounds to this issue:

1 — The customer manually adds the dependencies in the Cartfile: We chose this solution for the popular frameworks like Starscream. The issue here is that we cannot control the version of the dependency the customer is setting in his Cartfile.

2 — Add the dependencies directly in the project: For WebRTC for instance, we included a static library in our framework

3 — Use some git submodules: This allows us to have total control on which version we want to use. This can be a solution for open-source projects too, only until you need to implement some specifics behaviors. In that case, a fork can be useful, but can really be a burden to maintain and keep the sync with the upstream project.

4 — DIY: At first, we were avoiding implementing our own components by using popular libraries when we could. However, after experiencing several crashes and unwanted behaviors we decided not to rely on third parties. As we never use 100% of a framework’s features, we decided to build internally the maximum number of components we can afford to, so we can patch and maintain them ourselves, and fully use them in our Framework. So bottom line, made with ❤ @ Streamroot.

Today we distribute our SDK via Cocoapods and Carthage. We uploaded a Carthage archive on an Amazon S3 Bucket. You can read more about how we ship our SDKs on our blog here.

Conclusion

Building a proprietary framework has different challenges than building an open source one. One is not easier than the other; however, there may be more information out there about on how-to and best practices in the open source world.

Although some issues have been mentioned here, more issues arise with the different use cases every company faces. The good news is, some of the issues are being solved. For example, handling universal frameworks will be much easier with the latest XCode, bitcode resolves some architecture issues, and it’s becoming more and more simple to upgrade a swift project to newer versions.

At Streamroot, we are continuously working on improving our framework and making it easy for our customers to use. As mentioned earlier this is only one part of it, so feel free to share your experience -we’d be happy to hear from you.

--

--

Lamine Ndiaye
Lumen Engineering Blog

I am a passionate iOS Engineer working at Streamroot, helping companies face the challenge of delivering high-quality OTT video at scale.