Unit testing with UIDocumentPickerViewController – An Un-Googlable Bug

TLDR – If your unit tests crash with DocumentManager service tried to send a message to a deallocated host proxy, make sure you’re dismissing any presented instance of UIDocumentPickerViewController.

In our Callisto Xcode project, we’ve got a lot of unit tests. Like over a thousand. We’re at the point where if tests have a 0.1% chance of failing, then it happens every time. Our tests need to be really rock solid, or there’s no way we’ll get a clean run which is the only way CI will let a build through.

Some time ago, we started noticing the occasional test failure with an uncaught exception:

DocumentManager service tried to send a message to a deallocated host proxy

It would crop up when running test both locally and in CI. With so many tests, we get the weird edge case now and then, but they’re not worth the time to track down. We ignored it. With the recent update to Xcode 14.3 and macOS 13.3, the DocManager exception went from occasional annoyance to ‘omg, this happens every time I unit test on iOS’. So now we have to fix it.

But what’s a DocManager? IDK – there’s nothing with that name in our code and nothing in the docs about an Apple framework called DocManager. Looking through the traceback, it’s pretty obvious that it’s some kind of internal Apple thing. Surprisingly, a Google search for this error returns absolutely no results. That’s never a good sign.

But what’s a DocManager do? Callisto is UIDocument based, so maybe DocManager manages documents? A bunch of the unit tests open instances of a UIDocument and aren’t 100% about cleaning those up, so maybe some housekeeping will help. Cue the plumbing montage. We added a some bookkeeping to make sure that any open UIDocuments were closed at the end of each test, so now we’re sure there are no dangling UIDocuments. No impact – still the DocManager throws the exception.

This particular problem is a huge pain to track down. Somewhere in the tests, things get into a bad state. Later, while another test is running, some background thread discovers the bad state and throws an exception. The cause of the crash and the actual crashing are quite loosely coupled, making it hard to pin down the culprit. It also means that the offending tests will run just fine by itself, but only cause a crash when a large number of tests run, giving the background issue time to percolate to the surface.

After a couple hours of testing the tests and narrowing down which ones fail, it started to look like UIDocumentPickerViewController might be involved. We’ve got some ViewControllers that open a docPicker and get feedback via the docPicker’s delegate methods. To test those, we programmatically tap a button to cause the dockPicker to be presented, get a handle to that docPicker and call the delegate with the docPicker and some fake results. This works great for testing! Except for that pesky exception that gets thrown now and then.

If we take a closer look at the exception, we’ll see there’s a little more info attached in the userInfo dict, specifically a file and line number, DOCRemoteViewControlller.m line 42. Not that we have access to the source, but the name of the file, “Remote View Controller”, does offer a hint of its purpose. Those docPicker view controllers offer our app an escape hatch out of the sandbox and into the rest of the file system on the device. In my limited understanding, the docPickers interface with a separate process that manages file system access, so they actually represent some state from another (remote) long lived process. In the end, these dangling, un-dismissed docPickers were the root of the problem. Make sure all your UIDocumentPickerViewControllers are dismissed properly and the problem goes away.

Whew!

Excuse me waiter, there’s a bit of hay in my water. #RabbitsOfMastodon

A big ruby eyed white rabbit looks at the camera, expressing her impatience because there is a large hay cake in her water bowl.

Philosophical Dragon

MarsEdit 5

Tried to pay for an upgrade to MarsEdit 5 this morning, but it wouldn’t let me. The web store said I had purchased MarsEdit 4 too recently, and it gave me a free upgraded to version 5. Nice!

Greg Garcia’s Sprung on Amazon Prime Video is quite good. He’s the creator behind My Name is Earl and Raising Hope. If you liked those, Sprung will be right up your alley. Some familiar faces too. So good.

The Thinking Brick

A New Watch!

I took the plunge and ordered a new Ultra Apple Watch sight unseen. I’m very excited for a new wrist computer, especially with a big orange button! Seems weird though that there’s no titanium watch band to dress it up. I guess Apple is going full tilt on the outdoor adventure lifestyle to start. The fancy matching titanium band will probably show up in the spring.

(I desperately want to make an Ultraman joke, but obviously I’m no Ultraman.)

Holy Shift! Koenigsegg’s New Transmission Is a 6-Speed Manual and a 9-Speed Automatic

I always wondered if someone would build a clutch-by-wire system like this where there’s a gear shift and a clutch, but not physically linked to the drivetrain. This Koenigsegg gearbox sounds like just the thing. The problem now is that we’re all switching to gear-less electric motors, so will this come to electrics too? Make ‘em feel like a Miata? I guess Dodge is going to try with the eRupt.

Holy Shift! Koenigsegg’s New Transmission Is a 6-Speed Manual *and* a 9-Speed Automatic:

"We still are in the process of developing it, but it's already crazy good. When we are done with it, I don't think anyone will be able to tell it apart from a traditional manual. That's the objective. It should feel like a mix between a Mazda Miata and a Ferrari gated shifter. The best of the two worlds."

Serendipity

On the occasion of a blind squirrel finding a nut.

Wordle 426 2/6
⬜🟩⬜🟨⬜
🟩🟩🟩🟩🟩

Sponge worthy?

My favorite new unix too is definitely sponge. It’s from the moreutils suite, but the easiest way to get it on your Mac is a quick brew install sponge.

You know how sed can edit a file in place? Dope, right? sponge makes it easy to do that with all the regular unix tools. If I want to fix a DOS text file by removing the carriage return, I always try doing

cat my_file.txt | tr -d "\r" > my_file.txt

But that doesn’t work because it starts writing to the file before it’s all read and a lot of times, the file ends up empty. sponge to the rescue!

cat my_file.txt | tr -d "\r" | sponge my_file.txt

sponge will read everything from standard in and then write it out to the given file, so this works like you want it to, even for big files.

Also great with jq for prettifying JSON files in place too.

Three Habits

The three hardest habits to break are heroin, carbs and a steady paycheck.

– Nassim Nicholas Taleb (paraphrased)

Illinois Tech ‘spinout’ startup Influit Energ | EurekAlert!

A research group out of Chicago has developed “liquid batteries”. I have no idea how the chemistry of this works, but it sounds like you could refuel an electric car a lot like you’d gas up an ICE car, and in a similar amount of time. And about a billion other applications.

Illinois Tech ‘spinout’ startup Influit Energ | EurekAlert!:

The unique high-energy density liquid format of the NEF flow batteries allows use of the same fluids in different devices, meaning fluid, charged at the recharging station from renewable energy sources or a grid, can be used to rapidly refuel vehicles, or for stationary storage and other large portable applications,” Timofeeva says. “Discharged fluid can be returned to a recharge/refuel station for recharging or be charged inside the device by plugging into the power source.

Muppet Songs: Ernie - Rubber Duckie - YouTube

Today’s earworm…

Muppet Songs: Ernie - Rubber Duckie - YouTube:

Daring Fireball: ‘What’s the Deal With Water Bottles?’

From the New Yorker via DF, a delightful survey of comics and their water bottles. As a kid with cable, I watched a lot of standup, so I found this extremely interesting. And the presentation is molto bene – the animated magazine version of the web.

Daring Fireball: 'What's the Deal With Water Bottles?':

Tricorders

When I was a kid, I expected my tricorder would look like this

Yokogawa AQ1200

But it turned out like this

158438 phones review iphone 13 pro review a lesson in refinement image5 ws2nobt8ak

Which still seems weird.

Via WIL WHEATON dot TUMBLR dot COM - vizreef: Yokogawa // AQ1200 Multi Field...:

Two’s company. Get lost nerd.

The MYSTERIOUS Error.

Callisto is a Catalyst app, mostly running iOS style code on a Mac. But there are some things that Catalyst apps can’t do – like run a python server as another process. But Mac apps can!

There are a lot of instructions around the Internet about how to use AppKit code within your Catalyst app to do those Mac things that Catalyst doesn’t cover yet. This article from Steve Troughton-Smith was our guide.

We also want to do auto-updates like a regular Mac app. We’re distributing outside the Mac App Store for Reasons, so we’re in charge of our own updates. Yep, dear reader, that means Sparkle, which should be fine, right? So we get the Sparkle framework and link it in to our AppKit bundle which is a plugin for our main app. Compile, run and 💥. This error is staring at us from the console.

code signature in (<full path redacted>/Sparkle.framework/Versions/A/Sparkle) not valid for use 
in process using Library Validation: not allowing mapping of development code into production process

What does that mean? After some furious googling, we’re left empty handed and confused. The worst kind of error is that special, new error that no one else has ever seen. Bollocks.

After some futile futzing around in the dark, I give up and burn a ‘code level’ support ticket with Apple. I send a fairly detailed outline of the problem and the next morning I get back a nice response asking for more details. In particular, how are the app and the framework code signed? (You can check that with codesign -vvvd <.app or .framework>.)

And immediately, I see it.

App Signature:

Authority=Developer ID Application: Oak City Labs, LLC (XXXXXXXXX)

Framework Signature:

Authority=Apple Development: Continuous Integration (YYYYYYYYY)

The app is signed with a production identity while the framework is signed with a development identity. That’s what that weird error meant about not allowing mapping of development code into production process. The codesign docs point out how frameworks and apps need to both be signed with the same Team ID (which I checked), but don’t mention this distinction between production / development signing identities.

I update Xcode so that the AppKit bundle was being signed with the same production identity and all is well! We are fully Sparkle enabled! Curiously, the AppKit bundle has always been signed with the development identity before, yet it loaded fine at runtime. That must be one of those fine distinctions between a bundle and a framework that I don’t quite understand.

But now we celebrate! 🎉🎉🎉

Throw some Results

Errors, we’ve all got ‘em. What do we do with ‘em?

Swift has a couple of different error handling patterns. There’s try and catch which I always think of as kind of the nuclear option. Something went wrong, the code blows up with a throw and our hero, the intrepid programmer, has to deal with it right here, right now. And then there’s Result, an elegant weapon for a more civilized age. Call a method and get back a Result, then deal with it as you like – check for the value, handle the error or maybe even ignore it altogether. Depending on your situation, one of these is likely to suit better than the other.

But sometimes the situation changes. Maybe you wrote some code that should obviously throw(or calls some library code that throws), but it would really make a lot more sense to return a Result. As it turns out, it’s really easy to convert between try/catch and a Result type.

Imagine you have some code that can throw. Maybe even it always throws, because it is a badIdea(). But you need to have a Result instance instead. The Result type has an initializer that handles this perfectly. You hand it a closure that returns the Success type for the Result and any error thrown by the closure is used for the Failure case. Basically, they’ve wrapped up all the boilerplate in this initializer, and you get a very concise way to convert try into a Result.

enum HorrendousError: Error {
    case veryBad
}

func badIdea() throws -> String {
    throw HorrendousError.veryBad
}

let result = Result<String, Error> { try badIdea() }

The full signature of that initializer is Result.init(catching: () -> Success). The single argument is a closure returns the Success type – here that’s a String. If the closure throws, then that gets wrapped up as the .failure case.

How about doing the reverse? You’ve got a result type, but you’d rather access the value in a try / catch sort of way. The Result type itself has a accessor method for just such an occasion!

let value = try someResult.get()

If the Result type is the .success case, it just returns the successful value. If someResult is a .failure case, the get() method will throw. This is especially useful if you want to access the result and ignore any errors:

let optionalValue = try? someResult.get()

This is also handy in testing when you expect a Result type to be successful. Use the get() method to access the value and let XCTest’s support for tests that throw to handle the error cases. This can make tests shorter and easier to read too.

For me, discovering the interchangeable nature of try/catch and the Result type was a bit of an ‘aha!’ moment. You’re not locked in to on type of error handling or another. With these convenient converters, you can fluidly move from one type to another as needed. And without a lot of boilerplate code for the conversions!