Category Archives: SwiftUI

The Danger of Playing it Safe

It’s an old chestnut that Swift developers love to sink their teeth into: Should you force unwrap optionals? Everyone has an opinion about it, and I’m going to state mine as clearly as I can — forcefully unwrap early and often.

Of course, this is just one opinion. We even disagree about it within our two man Agenda team, so there is probably not much hope for the world. But I want to explain when and why I think it is good to force unwrap, and give a recent real world example that demonstrates the cost of not doing it.

First, my reasoning. I think you should force unwrap in any situation where logic tells you that an optional simply cannot be nil. A standard example might look like this.

var count: Int?
count = 0
print("\(count!)")

Could count end up being nil in this case? Perhaps, through some strange memory corruption or solar flare. Do I want my app to continue to run if count is nil? No. To put it bluntly, that is cancer. The app would be in an undefined state, which may well be dangerous. I don’t want it deleting user data or causing any other havoc — it should just crash. I will be informed of the problem, and it can’t do any more damage.

But what is wrong with the softly, softly approach? Shouldn’t we be defensive here? Here is how you could do it.

var count: Int?
count = 0
if let count = count {
    print("\(count)")
} else {
    print("error!")
}

There are several problems with this. Firstly, it is more verbose, making it more difficult to read. Secondly, and even more importantly, it disguises developer intentions. It says to the next developer “count could conceivably be nil, so we will check for it”, even though it was clear to the original developer that it should never be nil in any well-defined circumstance. Lastly, where is the app at after this code runs? Who knows? The state is completely unclear.

So be assertive with forced unwrapping. If there is no case where the optional should ever be nil, force unwrap. But don’t force unwrap in cases where there is a valid situation in which nil could arise. If a foreseen error could lead to nil, that requires explicit handling.

I mentioned that I wanted to finish off with a recent real-world example. When Apple announced iOS 14 would ship, they only gave about a day’s notice, leaving most of us scrambling to get ready. For Agenda, we had done a lot of work on widgets, but we had never had them in a beta, or extensively tested on the device. That was planned for the week or so that we usually have following release of the Xcode GM, but before the new iOS is available. Alas, in 2020, that was a luxury we didn’t have.

So I merged our branches in Git, and uploaded the binary. Downloaded from TestFlight, and — you guessed it — the widgets were broken. It was just showing a placeholder, which I later learned is what you get when a widget crashes.

I dug into the code, and I found that the problem was an incorrect file path. I had been testing with a special development target, and hadn’t properly tested the production target. The faulty code was as follows:

let identifierSuffix = (Bundle.main.object(forInfoDictionaryKey: "AgendaIdentifierSuffix") as? String) ?? ".development"

It is attempting to get a string from the Info.plist, which it will then use in the aforementioned path, but I had forgotten to put the string in the Info.plist. That wasn’t the real crime though: As you can see, in the case of nil being returned, it tries to play it safe by falling back on the development target’s string.

This was bad. It meant the production app had the wrong path, so it crashed, but I didn’t see the crash until very late in the development cycle, even though it was there the whole time. And the cost? I probably wasted an hour or two uploading the binary, then sluthing the problem. “What’s an hour or two?” I hear you ask. Well, Apple were pushing us to submit our new widget feature so that Agenda could be considered for featuring. I don’t think two hours was enough to jeopardize that, but you never know. I’m sure the App Store is a madhouse on release day.

Here is how I think the code should have been written.

let identifierSuffix = (Bundle.main.object(forInfoDictionaryKey: "AgendaIdentifierSuffix") as? String)!

It is simpler, clearly stating that I am assuming the entry is in the Info.plist, and the widget only works if that assumption is fulfilled. If there is no entry in there, the widget is entirely useless, and should crash before causing more damage. And, with this code, it will crash early in development. I would have discovered the issue as soon as I tried to run a debug version, and fixed it long before it hit the App Store.

(Update: It has been pointed out that the last line of code is a bit convoluted. It would read better as

let identifierSuffix = Bundle.main.object(forInfoDictionaryKey: "AgendaIdentifierSuffix") as! String

That’s very true. I wanted to change the original code as little as possible, to stay on message. The point is the same: defensive coding did more damage than good, and an assertive force-unwrap would have been better.)

Why TextView is my SwiftUI canary

WWDC is just around the corner, and most of us will be fully focussed on what happens with SwiftUI. The unveiling last year generated great excitement, but there are still plenty of holes to be filled before it is a solid replacement for UIKit and AppKit (…or even a solid complement).

One of the big questions on my mind is how well the fully declarative approach scales to complex apps. You can already build quite reasonable portal apps for your favorite web service with SwiftUI, which is 90% of the iOS app market, but I am much more interested in the other 10%. Can you build an advanced iOS app or serious macOS app with SwiftUI? Could you develop an app like Keynote or Xcode using SwiftUI?

I spent a lot of time this year — probably more than desirable — working with the framework. At Agenda, we decided to ‘kick the tyres’ by adopting it in a new internal tool that we wanted to develop. The tool was not trivial either: it loaded significant amounts of data, shared it between multiple users with LLVS backed by the CloudKit public store, and allowed collaborative editing and discussion. Effectively, it was like a fully decentralized web app, built with native UI.

Ambitious? Sure. Too ambitious? Undoubtedly. But we learnt a lot before abandoning the idea. And it did actually work, though after a while we concluded ironing out the performance issues was not really worth the effort.

And this brings me back to the present day, and what we should be looking for at WWDC. One thing I learned from our SwiftUI tool is that a fully declarative style presents some performance challenges. By its nature, SwiftUI is based around view tree diff-ing. When something in the model changes, it triggers a binding to fire, which in turn invalidates the view tree. In order to update the screen, SwiftUI now compares the new tree with the old, and determines what changes are needed to the scene that the user sees.

This process is fast for a few standard views, but there are bigger challenges looming. For example, take a large array of values that you want to present in a List. We encountered some serious performance issues when updating data in such a List, as SwiftUI would attempt to diff thousands of View nodes. You could restructure your UI in order to avoid such large lists, but that is a bit of a cop out, because the technologies SwiftUI is designed to supplant can handle such large lists.

The problem is perhaps even more apparent in a text view. We briefly saw a TextView type appear during the SwiftUI beta in 2019, but it didn’t make the final release. My guess — and it is just a guess — is that someone in Apple concluded that it worked fine if you were presenting tweets, but things fell apart when you wanted real-time editing on Chapter 5 of “War and Peace”.

You can bind a TextField to a model String property, and it will work fine, but do you really want to bind the contents of a TextView to an AttributedString the length of a novel? Diff-ing that is not going to cut it.

One way they could take SwiftUI in order to solve this is to introduce a new type of text model, one with internal hierarchy that makes diff-ing extremely efficient. Think about a tree-like structure, so that it could very quickly establish which subtrees were altered, and ignore all other parts of the tree. Effectively, it would be introducing a structure similar to the view tree itself, but inside the model.

This option would see NSTextStorage replaced with a highly diff-able structure, but it may also be possible to introduce a similar approach at a different level. Perhaps the model data would be left unchanged, and a specialized TextStorageBinding type added, which would embellish the model with metadata to make diff-ing efficient. Or could they even introduce View types such as Paragraph which shadow the model text content, and allow exactly the same diff-ing mechanism to be used as in the rest of SwiftUI?

Some of these proposals may seem far fetched, but this is exactly how we structured the text in the Agenda notes app. First, our model has finer granularity than just putting the complete text of a note into a single property. Each note is broken into paragraphs, meaning that a user edit doesn’t require a complete refresh of the whole note. Instead, just the affected paragraphs can be saved, refreshed on screen, and synced to other devices. This approach does require extra bookkeeping — we have to pass around the ‘edited’ paragraph identifiers — but it is this type of data that SwiftUI will no doubt need to introduce if it is ever to host an editable TextView containing the whole of Tolstoy’s classic novel.

At this point, it is anyone’s guess which direction SwiftUI will take. Perhaps Apple figure the days of complex apps are over, and they are not going to do anything to address such concerns. Or perhaps it is just a question of priorities, and we will see how they intend to tackle these scaling issues at WWDC2020. Either way, I will be seeking out the documentation for TextView; that’ll be my canary for where SwiftUI is headed, and whether it will soon evolve into a capable replacement for UIKit/AppKit.