Inspecting watchOS

Greetings, Apple Watch tinkerers! Today we’ll cover the ways you can use Reveal to take a peek inside apps running on watchOS 2 – made by you or Apple – and see how they tick.

Let’s quickly review the moving parts of a WatchKit app:

  • WatchKit Extension contains code that developers write and debug.
  • Watch App is a wrapper that contains the Extension and most of the resources used to construct the UI, like Storyboards and graphical assets. It also contains code that communicates with the Extension and renders the app’s UI as a UIKit view hierarchy.

As of watchOS 2, these two parts run in separate sandboxes right on the watch, but developers still have no control over the Watch App counterpart. This makes it difficult to inspect an application’s user interface, since the extension doesn’t manipulate the objects rendered on screen directly. WatchKit applications don’t “own” their UI in the way iOS apps do, instead they use WKInterfaceObject instances, which provide a “write-only” proxy interface. Due to sandboxing, it’s currently impossible to inspect an app that’s running on device without jailbreaking or performing other modifications. Today we will focus on inspecting apps running in the watchOS simulator.

Injecting the Reveal library

To inspect a view hierarchy with Reveal, we need to inject the Reveal library into the process that owns that hierarchy. In our case, we’re not compiling or running the process in Xcode, so we’re limited to Dynamic Loading: a technique similar to what’s described in the Reveal Integration Guide. We’ll start by demonstrating how to use this technique to inspect apps built into the watchOS 2 Simulator, since it’s actually easier than inspecting your own apps.

First, launch Xcode and open a watchOS project. Then open the watchOS simulator and ensure the watch face is visible.

Apple’s own apps do not follow the WatchKit two-process architecture; instead, each app is run in a single process, similar to iOS apps. In order to attach to one of these apps we need to find out the name of the process. This involves some trial and error, but the watch face and the rest of Apple Watch home screen is rendered by a process called Carousel.

Attach the LLDB debugger to the Carousel process using Debug → Attach to Process by PID or Name… menu in Xcode. Type Carousel in PID or Process Name field of the dialog and click Attach.

In a few moments, Carousel will appear in Debug Area toolbar, indicating that the debugger has been successfully attached. Click the Pause button there to pause execution of the process and give you control over it via the debugger’s console. You’ll notice that the watch’s second hand has stopped.

Now we can inject the Reveal dynamic library into the process. Paste this into the Debugger Console and press Return:

expr (Class)NSClassFromString(@"IBARevealLoader") == nil ? (void *)dlopen("/Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries/libReveal.dylib", 0x2) : ((void*)0); [(NSNotificationCenter*)[NSNotificationCenter defaultCenter] postNotificationName:@"IBARevealRequestStart" object:nil];

This command instructs the process to load Reveal’s dynamic library and start the Reveal server, assuming you have Reveal installed at /Applications/Reveal.app. This approach is documented in the Reveal Integration Guide.

Note: Instead of pasting this long command every time you want to manually inject Reveal into a process, you can use an LLDB macro. Check out this blog post to learn more.

Now, click the Continue button in the debugger’s toolbar to allow the process to continue execution. You’ll notice the watch’s second hand moving again. At this point, you can launch Reveal. If you have enabled automatic connection to Simulator, Reveal will connect to Carousel immediately. Otherwise, you can use File → Connect to → Carousel on Apple Watch Simulator menu in Reveal to select it as you would with any other instrumented app.

Carousel has a fairly complex view hierarchy as it also hosts other apps’ UI, similar to the SpringBoard process on iOS. Feel free to explore and experiment with it!

Inspecting your own apps

As we’ve covered previously, third-party WatchKit apps are executed as two processes running in concert: a WatchKit Extension and a Watch App. When you’re running your own WatchKit app in Xcode, the debugger attaches to the Extension, not the App. In order to inspect the view hierarchy, we’ll need to attach to the App counterpart manually.

Start by opening your WatchKit project and running it in the Watch Simulator.

This time, instead of using Debug → Attach to Process by PID or Name… dialog, we’ll use Debug → Attach to Process → … menu. You’ll see two processes in the Likely Targets section of the list: an App and an Extension. Select the WatchKit App.

This will attach the debugger to the WatchKit App and allow you to perform the same steps as covered previously to inject the Reveal dynamic library into it: click Pause, paste the injection command, click Continue. You’ll then be able to select <YourAppName> WatchKit App on Apple Watch Simulator in Reveal.

It’s actually possible to edit most UIKit properties of your WatchKit app’s view hierarchy as you would expect from an iOS app – and see these changes live in Simulator. Of course, you won’t be able to reproduce some of these changes from your WatchKit Extension, but it can still be useful to try out, for example, different font sizes or colours. Keep in mind that WatchKit apps don’t use Auto Layout, so changing a label’s font size won’t change its bounds automatically.

Conclusions

Using the LLDB debugger, it’s possible to inject the Reveal library into watchOS Simulator apps – both built-in and the ones you develop. It currently requires some manual steps, but it’s a great way to experiment and learn, or just to find out how all these cogs and springs bring the Apple Watch user interface to life.

Some advanced techniques exist which produce similar results on actual Apple Watch hardware (for example, patching the Watch App stub used by Xcode for your apps), but we’ll leave that as an exercise for you, dear reader.

Sample Snapshot Documents

We’ve included a variety of snapshots below for you to play with. You’ll need Reveal 1.6 or greater to view these files.

Vlas Voloshin
Vlas Voloshin
Developer
by Itty Bitty Apps