Reimplementing StoreKit Testing: How Objective-C Makes Reverse Engineering Easy
by Eshed Shaham
Frequently, during development, we find that our tools do not suit our use case. Most of the time, we either suffer the consequences or switch to better tools (if they exist). But occasionally there’s an opportunity to reimplement the tool in a way that’s better suited to our circumstances. This can be relatively easy in a dynamic environment like the Objective-C runtime, where some minimal reverse engineering can give you the understanding necessary to replicate a given functionality, while adapting it to your requirements.
StoreKit Testing
In WWDC 2020 Apple announced StoreKit Testing, a new way for apps to test in-app purchases. This was a cause for celebration for us, since testing purchases is a critical and notoriously fiddly part of pre-release tests for all our apps. Any improvement to the testing flow could save countless hours of manual testing and configuring sandbox accounts.
Unfortunately, the celebration was short-lived, as we discovered that Storekit Testing is limited when used with a running app (and not in a unit testing context). In this scenario, it must be manually configured via obscure menus in the Xcode interface, and each change to the configuration requires rebuilding the package, which costs more of the precious developer time that we were trying to save.
But we knew that a nice programmatic interface does exist, as it’s available to unit tests in the form of SKTestSession. This class exposes all the functionality hidden in Xcode’s interface through methods that can be called during runtime. We realized that if we could find out how it worked, we could copy its behavior and create an equivalent interface for in-app testing.
Reverse Engineering SKTestSession
In order to expand the interface, we first needed to understand it, and the best way to understand an object is to reverse engineer it.
The term “reverse engineering” evokes images of dark rooms in which hoodie-clad hackers huddle over screens full of apparent gibberish. Sometimes that’s the case, but when working with Objective-C, our familiar debugger gives us reverse-engineering superpowers. Let’s see how we used these powers to find out how SKTestSession
does its magic.
We started our investigation by simply creating an instance of SKTestSession
inside a test,
putting a breakpoint underneath it and eyeballing the object:
There are two interesting details here:
1) The SKTestSession
object has an NSXPCConnection
member. These objects are used as communication channels between processes. Maybe this is how SKTestSession
communicates with the StoreKit daemon? Another property reveals the answer to us:
2) The serviceName
property of the connection is com.apple.storekit.configuration.xpc
. Notice the terms storekit
and configuration
which strongly hint that we are on the right track.
After identifying the channel of communication, our next step was to find the interface between the two processes. Luckily for us, NSXPCConnection
uses ordinary Objective-C protocols to define its interfaces. The interface can be found under the remoteObjectInterface
property of the connection, which is easily accessed in the debugger console by using:
po [testSession->_connection remoteObjectInterface]
This prints all the functions declared in the interface.
Again, lots of interesting information here. We have the signature of the protocol’s methods — specifically, the signature of the completion
argument (referred to as a reply block). Here’s why that’s important:
Reimplementing SKTestSession
With our new knowledge of the interface, we could now create our own connection and try to set up our own Storekit Testing environment. The method for doing that is
- saveConfigurationData:forBundleID:completion:
. We found out above that it receives NSData
, NSString
and a callback block, which itself receives an NSError
. So we define a protocol with this method:
Here, we had a small obstacle: the initializer of NSXPCConnection
that we want to use is unavailable on iOS, but that’s easily circumvented with a category that re-exposes it:
(Note: Exposing private Apple interfaces like this is fragile and violates App Store rules, but is perfectly legitimate when debugging. Just be careful you don’t accidentally ship it in your production app.)
The following code creates the instance and tries to set a configuration:
Which prints Configuration set successfully!
as expected. :)
And so, we managed to reimplement the ability to set our own StoreKit Testing configuration at runtime within our app! All that was left was to add the other methods to our MyInterfaceProtocol
, and that was it — we reimplemented a more flexible SKTestSession
, without looking at a single line of assembly.
We now use the resulting module in Lightricks, successfully speeding up pre-release manual tests and development of features involving purchases. Our developers can switch between scenarios like purchase failure and Ask to Buy, reset the transactions and lots more, all within the convenience of a running app – all without rebuilding.
—
Create magic with us
We’re always on the lookout for promising new talent. If you’re excited about developing groundbreaking new tools for creators, we want to hear from you. From writing code to researching new features, you’ll be surrounded by a supportive team who lives and breathes technology.
Sound like you? Apply here.