Apple’s WatchKit Performance Tip Versus An Admonition From Mister Spock

Donald Knuth once said “Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time”.

With that in mind, let us consider two of the five WatchKit performance tips recommended by Apple. The last part of Knuth’s quote is also relevant to both of these tips, but I’ll defer that discussion to the end of this post.

Update only what has changed: Apple’s first performance tip stipulates that “A well-performing WatchKit app requires minimal traffic between iPhone and Apple Watch.” Apple continues with a second tip that says “Update only what has changed”. This essentially means that values on user interface objects (such as the text shown on an Apple Watch app screen through a WKInterfaceLabel object) must only be updated if the value changes. For example, if a hypothetical DadBod watch app set the text of a label called LabelCaloriesConsumed to “Calories: 2100/2400”, the app shouldn’t attempt to set the label value again until the user consumed more calories and increased their consumption count to something more than 2100.

Dangers of worrying about performance: For many programmers, “Update only what has changed” might seem like common-sense and Apple’s stipulation is likely to nudge more developers to implement the “update only what has changed” guideline. This will minimize traffic between the iPhone and Apple Watch. However, many developers reading Apple’s performance tips may not be aware of other WatchKit technical facts and that can make Apple’s performance tip a risky proposition. To quote Spock, “Insufficient facts always invite danger

Naïve Programmer-X ignores performance tip: One archetypal naïve developer might ignore this performance tip and repeatedly set the label value to “Calories: 2100/2400” repeatedly. This would result in a slightly adverse performance impact, but on the plus side, the app will function correctly from a user perspective.

Well-intentioned Naïve Programmer-Y implements performance tip: An archetypal well-intentioned, but naïve developer might implement the “Update only what has changed” performance tip without caution. One popular blog post suggests a “View Model” approach and replacing traditional calls like label.setText(“newValue”) with calls to label.updateFrom(“oldValue”, “newValue”). They also provided an implementation of the updateFrom method with the code shown below. However, their code may not work correctly on all occasions and so I’ve listed it in strikethrough style.

extension WKInterfaceLabel : Updatable { func updateFrom(oldValue : String?, to newValue : String?) {   if newValue != oldValue {self.setText(newValue) }}  }

The risks of minimizing traffic: Well-intentioned programmer-Y might see some performance gains, but they run the risk of having the app function incorrectly. Apple’s documentation says that “attempts to set values for interface objects are ignored” after the didDeactivate method is called. In other words, setting WKInterfaceLabel text will be useless if the controller is inactive. In walking through code, I’ve verified that setText code in controller methods may not always set the text value of labels. If the Apple Watch screen times out (as it does very often for almost every watch user), the app might execute setText code, but the setText call is ignored by the watch.

This means that the developer’s controller class might think that the WKInterfaceLabel’s value was updated to the latest value, but the watch might have ignored the new value. If the user pressed the crown to activate the screen again, Programmer-Y’s app will show the old value (e.g. Calories: 2100/2400) but refuse to update the label text value because it thinks that it showing the new value (e.g. Calories: 2500/2400).

The WatchKit API doesn’t provide any way for the app to know what exactly the watch is displaying. Apple’s documentation says “Communication between an interface object and the corresponding view on Apple Watch is one way, with information flowing from your WatchKit extension to Apple Watch. In other words, you set values on an interface object but you cannot get the current values of its attributes.” The end-result is that Programmer-Y might “Minimize Traffic” and get some performance gains with a naïve implementation of the “Update only what has changed” performance tip. However, the user of this app might see stale and incorrect data/text in the app and the app would have no way of knowing what was being shown to the user. This would be a worse user experience than the one provided by naïve programmer-X’s app and is a good illustration of Donald Knuth’s point that a focus on efficiency could potentially have a “strong negative impact”.

The critical 3%: Knuth’s essay says that “We should forget about small efficiencies, say about 97% of the time” and then starts his next paragraph by saying “Yet we should not pass up our opportunities in that critical 3%.”. The two statements complement each other just like Spock’s admonition complements Apple’s performance tip. The iPhone communicates with the Apple Watch using Bluetooth or WiFi and traffic between the two devices can be slow and also result in greater battery usage. So I would assert that minimizing traffic would generally fall in the “critical 3%” and that programmers should neither follow the Programmer-X model nor the Programmer-Y model.

Optimize with caution: One starting point to minimize traffic between the iPhone and Apple Watch is to maintain information about interface objects (e.g. calorie values pertinent to a label on the watch screen) in the WatchKit extension of the watch app and only set watch interface objects to new values if the new value is different from the old value known by the watchKit extension. In addition, WatchKit extension code should only update interface object state if it believes that the interface controller is active at the time.

The WatchKit extension code could do this by maintaining an isActive flag that is turned on in the WKInterfaceController’s willActivate method and turned off in its didDeactivate method. This will help in making sure that the extension code doesn’t hold on a new value even though the watch is actually showing an old value. This eliminates almost all risks, but it is possible that some version of the Watch OS might ignore setText calls after the screen times-out and before the didDeactivate method is called.

A conservative approach to this potential problem might be to turn off the optimization when the interface controller is re-activated, but keep optimization turned on during the rest of the session.

The Bottom-line: Minimize traffic between the iPhone and Apple Watch, but understand your WatchKit extension code and make sure that you’re not sacrificing correctness for efficiency. Users appreciate performance gain even more if the app works correctly and if it doesn’t display stale, wrong data.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: