Rollout Swift Support – Under The Hood

Eyal Keren | November 3, 2016

How Rollout patches live Swift apps.

A while back I published an article on how Rollout works under the hood which describes how Rollout works for objective-c apps. Now that Rollout also supports Swift, I wrote this article which covers the technical details of how Rollout patches Swift apps using Swift “pseudo method swizzling”.

You just finished fixing a critical  bug in your iOS app and submitted to the App Store for approval. Congrats!

The problem is that can take anywhere from a couple of days to a couple of weeks before it’s approved! If this is an urgent bugfix, even a few hours can be an unbearable amount of time to wait.

That’s where Rollout comes in. With Rollout’s SDK in your app, you can instantly push code level updates, such as bug fixes or diagnostic code, to native iOS apps in production. Yes really, and totally legit by Apple’s guidelines. Rather than waiting for App Store approval to push code updates, with Rollout can you find and fix issues in live apps in minutes.

In this article, I elaborate in detail on how Rollout works it’s magic under the hood (techie stuff) when patching Swift apps.

The heart of Rollout’s Swift patching magic lies in a technique we call Pre-SIL instrumentation.

SIL stands for Swift Intermediate Language, it is generated from the Abstract Syntax Tree. This intermediate form is used by the Swift optimizer to perform Swift language specific optimizations prior to generating the LLVM IR.

Rollout instrumentation needs to happen before the optimization phase so it will work even if the method was inlined, the dispatching was de-virtualized and more (I’ll write more about optimizations in Swift in the near future).

To add this code instrumentation Rollout runs the following flow:

  1. Replace the swiftc compiler with a proxy script
  2. When swiftc compiles a file, intercept the file and instrument it
  3. Identify all methods in file
  4. Instrument all methods with the patching mechanism

All of the above needs to happen without a cost in compile time, runtime and most importantly maintaining a consistent debugging experience (as if Rollout didn’t touch your code).

1. Replace swiftc compiler with a proxy script
First thing we need to do is wrap the compiling of Swift files. This is done with the SWIFT_EXEC  environment variable (see docs). If you have already installed Rollout with Swift support you can see this variable set in your Xcode build settings under user-defined:

swift blog 1

The proxy compiler is a simple bash script that captures the parameters and sets up the data for Swift files interception.

2. Intercepting the Swift file
Using DYLD_INSERT_LIBRARIES  we grab the call (see open) and are able to instrument the source file.

3. Identify all methods in file 
Using sourceKitten we get the AST tree of Swift code and method decleration location.

4. Method instrumentation
Given the method:

func doSomething(a:Int, b:Int) -> Int {
    // method original body
}

We add a prefix to the method, that looks more or less like this:

func add(a:Int, b:Int) -> Int {
    if Rollout_shouldPatch(ROLLOUT_a79ee6d5a41da8daaa2fef82124dcf74) {
        let resultRollout : Int = Rollout_invokeReturn(Rollout_tweakData!,target:self, arguments:
        [a,b, origClosure: { args in return self.add(a:args[0],b:args[1]);});
        return resultRollout;
    };
//
// original method body
//
}

The above is not the exact code that is added. Some optimizations and details are missing, but the general idea is the same.
The ROLLOUT_a79ee6d5a41da8daaa2fef82124dcf74  variable is the unique identifier of that method, and is set by the SDK as part of the patching mechanism

The Actual Patch
The method Rollout_invokeReturn  mentioned above activates JavaScript code that can delegate back to the original method if the user wants to run the original method, as we can see in this patch code example:

NumberTileGameViewController.scoreChanged = function (
    score // SwiftBox<Int>
) {
        self.originalImplementation(42);
};

Calling the origin method of

screen-shot-2016-10-27-at-5-09-18-pm

With the number 42.

Rollout utilizes The JavaScriptCore Framework. The Framework allows you to evaluate JavaScript programs from within an your native app It also lets you insert custom objects to the JavaScript environment. More details here.

In case you’re wondering, Apple specifically allows apps to download and execute code using Apple’s built-in WebKit framework or JavascriptCore API.

How does the App get the update?

Rollout’s SDK automatically checks to see if there are any updates (1) whenever the app is launched and (2) when the app enters foreground. This is done by making an asynchronous secure HTTPS request, in order not to cause any delay when the app starts. App update files are also cached. If a user does not currently have network connectivity the cached version of the update will be applied.

Furthermore, all app update files are signed using public / private key encryption. The SDK checks the update file and applies the update ONLY if it is authenticated.

Another decision that we make concerns how Rollout’s SDK is loaded. The SDK is designed to help you solve mobile app issues as they happen. When your application starts, the SDK fetches any current configurations from Rollout’s end point, applying them immediately once they reach a device. This is done asynchronously to avoid any possible delays that may affect user experience. Lean more about our FAQs.

Summary

This article only scratches the surface of Rollout’s mechanism. I decided to focus on hot patching in order to keep things simple and provide specific use cases. The ability to apply behavior on top of an application’s code provides our customers with interesting opportunities, such as ad hoc log generation (in order to track issues, even in production environments), dynamic analytics, A/B testing, remote configuration and more.

Rollout in Action Video

If you want to see a video that shows Rollout in action by adding logging on the fly and actually fixing a live bug in production, check this out – it should help you understand exactly what Rollout can do and how it works:
http://support.rollout.io/docs/video-fix-a-bug-with-rolloutio