Building a Better Ant Hill

Recently, I was tasked with answering the following question (actually two questions, but we’ll get to the second one at the end):

Is this:

@import Ant.Ant000.h;

going to compile faster than this:

@import Ant;

Restated more verbosely: in this era of modules, is it faster to import only the individual files you need from an module umbrella header, or does it make no difference, so you can rely on the simplicity of always importing the entire module in all your files?

I had always assumed the latter, but now I was being asked to prove it.

To do that, I made a new GitHub project, Import-Ant. Inside of it, you’ll find five Xcode projects: four test projects and a test builder project.

You may ask: why bother with a builder project? What do you need to build to conduct these sorts of tests?

Turns out, about 40,800 files.

I didn’t want differences between the two techniques listed above to get lost in the noise of a normal build, so I decided that my Ant framework (the thing to be imported) would have 100 header files — and a corresponding 100 source files — and my Hill iOS app (the thing doing the importing) would have quite a few more — 5,000 source files, each of whom would import one Ant header file.

To avoid having to make either those 200 header/source files, or those 10,000 header/source files, by hand, I wrote some code to do it for me, which resulted in the Builder project. There’s the AntBuilder class to make the Ant framework files, and the HillBuilder class to make the Hill app files.

Currently, there are four test projects that the Builder project will make files for:

  • 01 Import By Module
  • 02 Import Individually
  • 03 Import by Framework
  • 04 Import by File

The first two test projects address the problem described at the beginning of this post.

The second two test projects go more old school, converting the new module syntax back to straight-up C import syntax. Individual files:

#import <Ant/Ant000.h>

versus the umbrella header:

#import <Ant/Ant.h>

So instead of just one Ant and Hill project pair, there are four of them.

To test build times, I would reboot each time, open all four projects, wait for them to finish indexing, and then build one of them. After writing down that build time, I would clean that project’s build folder, then go back and start the cycle over again….

I built for Debug to keep it simple and I used the default Simulator target that came up when opening the project, either the iPhone 8 or the iPhone 8 Plus.

This command-line invocation helped:

defaults write com.apple.dt.Xcode ShowBuildOperationDuration -bool YES

It makes Xcode show the most recent build time in its user interface, like so:

Screenshot of portion of Xcode main window show build result 'Succeeded' with extra section '120.774s'

Here are average times:

01 Import By Module: 124.796s
02 Import Individually: 121.823s
03 Import by Framework: 126.342s
04 Import by File: 122.121s

The differences were between 0% and 4%, which I don’t find to be all that significant, for two reasons.

For one, I only built each project 3 times, and each test series had its own outliers. I suspect if I’d had the patience to build them 10 times, the differences would have smoothed out more. I’ve also since realized Xcode may take up significant amounts of CPU time even after its UI indicates that indexing has finished, lending more randomness to the proceedings.

For two, I actually built the first two projects 3X each separately before building all four projects for this post, and in that case, 01 Import By Module was faster than 02 Import Individually by 2%.

If you’re not convinced, you can certainly run them for yourself.

But for me, I think this proves there isn’t a significant penalty for using full module imports instead of trying to pick out individual module files to import.

The second question was whether this syntax influenced which files would be rebuilt if an Ant framework header was modified. Now, every individual Ant class is used by 50 Hill classes. If only, say, Ant000.h was modified, and only 50 Hill source files referenced it directly, would only those source files be rebuilt?

Turns out no. In all four test cases, two of which involved only references to specific Ant headers, the entirety of the Hill project was rebuilt if even only one Ant header was modified. Rebuild the module (in this case, the Ant framework), and everything that relies on any part of that module is also rebuilt by the current version of Xcode.

Sound right? I consider myself far from an expert in this area, so if anyone has any more information, feel free to leave a comment or ping me on Twitter. Thanks!

Restoring Transience

While doing some Core Data research, I came across my old GitHub project (from this post) demonstrating transient attributes.

I decided to update my project to current coding and Core Data practices, as an exercise, and I discovered a couple interesting, if minor, points.

1. Managed Object Context Uses Weak References

The whole purpose of the project was that, if I tried to fetch the same objects in two different Core Data contexts, the transient attributes wouldn’t be preserved.

But now, I found that even doing the same fetch in the same context would return different Objective-C objects, and thus would not preserve the transient attributes for any objects that I had made previously. What had changed? What was going on?
Transient app window showing three rows, with two having nil name attributes, and only the third having a non-nil name

What had changed, as far as I can see, is that Core Data is far more aggressive in deleting in-memory objects that don’t have any references to them except the context. Since my original project was doing a fetch every time it wanted the list of objects, and keeping no permanent reference to them, that meant that every object except the most recent one was going away and being recreated, and thus their transient attributes were not being preserved.

I’ve changed the project to keep its own list of the objects it has created so far, so they’ll stick around until I click the “Refresh” button.

This also means that I don’t need multiple contexts. I can just nil out my own list (and call reset on the context to be sure), and I’ll get new model object instances for my next fetch. This means that I can update my code to use the new NSPersistentContainer class and its main-thread-only viewContext for all my work, without worrying about maintaining multiple main-thread contexts myself.

2. There’s a Trick to Editing a Managed Object Model at Runtime

In my original project, the model was set to not use a transient attribute. If you wanted to test transient attributes for yourself, you had to go in and manually change the model file in my project, rebuild, and run it again.

This time around, I decided to do better.

So while I still left that attribute as non-transient on disk, I added some code to edit the model in memory before it is used, and tied that value to a new checkbox in the user interface. This, the comments in NSManagedObjectModel assure me, is totally allowed and supported.

Transient app window showing a new checkbox on the right labeled 'Transient'

Now, if you toggle that checkbox (which deletes the current list contents), you’ll change the behavior to either use a transient name attribute (so that refreshes will nil out the names) or a non-transient name attribute (so that refreshes won’t nil out the names).

The trick? The instance of the model you load from disk can’t be edited at all, even before its use in a persistent container. You have to make a copy of it.

3. In-Memory Stores Can’t Be Transferred

My original project used an on-disk persistent data store, but deleted it every time the app started up.

This time around, instead, I used an in-memory persistent data store, which resets itself on every restart with no muss, no fuss. (This is also very useful for unit tests.)

Now above, I said that if you toggle the “Transient” checkbox, all the current database contents are deleted, right? That’s because I have to throw away the current model, and make a new one with the transient attribute handled in a different way.

If I were using an on-disk persistent store, I could just reload the contents from disk using that new model.

But since I’m using an in-memory persistent store, there’s no on-disk backup to turn to.

And the APIs that Apple provides in NSPersistentStoreCoordinator, as far as I can see, do not allow you to detach an existing store from a coordinator and re-attach it to a new coordinator. It always assumes you can reload the store contents from a file on disk, which makes a new store object.

I’ve long believed that, even though Apple tends to say Core Data is an object management framework independent of storage mechanism, that’s just hogwash. No company I’ve ever worked at uses Core Data for anything serious without backing it with a SQLite database, and all of Core Data’s heavy-duty features are geared towards that configuration.

Here, as we can see, even their APIs favor one kind of store over another. Which is as it should be! But I wish they’d stop pretending.

Hosted vs. Unhosted Keychain Tests in the Simulator in Xcode 9

Did you know that there’s a regression in Xcode 9’s support for automated tests involving the Keychain?

To show you, I’ve updated the Secrets test application to have two unit test targets, “Secrets Hosted Tests” and “Secrets Unhosted Tests”. They are both unit test targets and, as advertised, the first runs against the Secrets application, and the second does not, relying on Apple’s built-in mechanism to run unit tests. This means the second target needs to include the necessary SAMKeychain files instead of relying on the app to provide them.

Both targets execute only one test, the exact same one: after checking and trying to delete any previous entry, it tries to save a string to the Keychain:

func testSave() {
    do {
        let _ = try SAMKeychain.password(forService: "MyService", account: "MyTestAccount")

        try SAMKeychain.deletePassword(forService: "MyService", account: "MyTestAccount")
    } catch let error as NSError {
        if (error.code != Int(errSecItemNotFound)) {
            print("\(error)")
        }
    }

    XCTAssertNoThrow(try SAMKeychain.setPassword("Foo", forService: "MyService", account: "MyTestAccount"),
                     "Throws!")
}

If you run these two tests in Xcode 8 in the Simulator against iOS 9 and against iOS 10, they succeed under iOS 9 and they succeed under iOS 10. Good so far.

But if you run them in Xcode 9, with the exact same Simulators, only the hosted tests succeed under all versions of the OS: iOS 9, iOS 10, and iOS 11. The unhosted tests only succeed under iOS 9. They fail under iOS 10 with the error -34018 (“A required entitlement isn’t present”), and they fail under iOS 11 with the error -50 (“One or more parameters passed to a function were not valid”).

Puzzling, isn’t it? It can’t be an entitlements issue or a parameter issue, because the exact same thing works when tested with a host application, and works under an earlier Xcode.

I have some anecdotal evidence that Xcode 8 in its early releases had similar issues with Keychain testing in the Simulator, fixed in later versions, so here’s hoping Apple can once again fix these issues in later versions of Xcode 9. I’ve filed a Radar about it.

Keychain Reaction

In my previous post, I talked about a sample app I made that demonstrates Keychain entry persistence across app relaunches and app reinstalls.

What I didn’t talk about was what a pain in the ass working with the Keychain is.

Over the years, I’ve seen a lot of codebases that included a lot of utility classes to make dealing with those ancient, ancient C-based Keychain APIs a little easier.

What I haven’t been able to find is a modern Swift library that does so. For Secrets, I wound up just copying in the files from Sam Soffes’s SAMKeychain library, which from my cursory googling seemed like one of the most recently-updated Keychain helper libraries. But it’s in Objective-C.

Any Swift-y Keychain libraries out there?

Secrets and Lies

Turns out, my entire previous post was trying to solve a problem that doesn’t exist.

I assumed, because I’d heard rumors about it and found this authoritative-sounding forum post, that Apple had indeed removed the persistence of Keychain entries for an app if a user deleted the app from their iOS device.

But while Apple did this in a beta release, they didn’t ship it in the final version (thanks to Nick Lockwood for pointing this out to me).

I verified this for myself with the new sample project Secrets, and you can too by downloading and running it for yourself, on both iOS 10 and iOS 11.

The app just shows a simple view with one text field, where you can type in anything you’d like. It is then saved in the app’s Keychain.

If you kill the app and come back to it, that value is again displayed.

If you delete the app and reinstall it, the value is still displayed. This is true both in the iOS 10.3.1 Simulator and the iOS 11 Simulator in Xcode 9 beta 4.

Now, in the iOS 11 Simulator, due to a bug, you can’t delete an app through the regular Simulator user interface.

So instead, you must delete it by hand from the file system, by going to ~/Developer/Library/CoreSimulator/Devices/, finding the UUID that matches that of the Simulator you’re using, then within that finding the UUID of your app, inside data/Containers/Bundle/Application.

But if you go through all that trouble in iOS 11 Simulator, then restart the Simulator and verify that the app is gone, then reinstall it…the Keychain entry is still there.

So you don’t need to use Shared Web Credentials the way I described in my last blog post. You can continue to rely on local app Keychain entries to keep your users logged in, even if the user deletes your app and reinstalls it.

For now.

Password Autofill and Shared Web Credentials

Update: See my next post for why the problem described here isn’t actually a problem.

In Apple’s WWDC 2017 session “Introducing Password Autofill for Apps”, the presenter describes this scenario: a user downloads an app, but is put off from using it when they’re required to log in with a username and password first.

I’ve worked at a number of companies built around a consumer service, and they all worry about this.

If a user who doesn’t use something like 1Password sets up a strong password, it’s likely they’ll forget it by the next time they need to log in. You can have them reset it, but…will they bother?

Apple’s solution assumes the user has logged into the service in Safari, either on that device or, if they have iCloud Keychain turned on, on another Apple device.

When they go to the username or password text fields in your app on iOS 11, in the QuickType bar above the on-screen keyboard, Password Autofill shows a little key icon to the right, and some text telling them that it now contains the username and password for that service. If they tap it, it will fill in the text fields with the saved credentials, and they’re on their way.

Password Autofill will attempt, via heuristics, to provide this information even if you’ve done nothing to support it in your app (besides connecting your app to your website, see below), though there are ways you can tweak your app to make the user interface connection more explicit.

If the user hasn’t logged into the service in Safari previously, or they did it on another device and they don’t have iCloud Keychain turned on, they’re screwed. Apple doesn’t have that information, and can’t offer it to them.

What if they’ve logged into the service before in that app, then deleted the app, then reinstalled it?

In the past, you could rely on the local app-specific Keychain entries surviving under such circumstances. So your app could still access the username and password from there, and automatically log the user in. But Apple took this away in iOS 10.3.

My understanding here is that you can restore that functionality by using something closely related to Password Autofill, but older: Shared Web Credentials (third-party tutorials here and here).

Both technologies require an Apple-recognized association between your website and your application; the WWDC session for Password Autofill and the documentation for Shared Web Credentials both spend a lot of time describing how to set up that relationship, which is also needed for Universal Links.

By using the Shared Web Credentials APIs, you can tap into that same reservoir of usernames and passwords that Password Autofill uses, but completely bypass the username and password fields of your application: you get the data yourself and can use it directly.

Why does anyone need Password Autofill, then? My hunch is that Shared Web Credentials didn’t take off the way Apple wanted — it does require more work, after all. So they made something even easier, something app writers might not need to do anything at all to support.

Now let’s think about it in the other direction: if Password Autofill is so easy, why worry about Shared Web Credentials at all?

Because Shared Web Credential let you go in the other direction.

If a user does log in via your app, you can use Shared Web Credential to push that information into Safari (and iCloud Keychain, if it’s on). Then, if they delete the app and restore it, you can still log them in automatically by getting that information back out of Safari. Note: doing so isn’t invisible: the user will be prompted with an Apple-provided confirmation alert in both cases.

So if you really want to make things as easy as possible for your users, it seems to me you’ll want to support Shared Web Credentials in both directions, before worrying about Password Autofill at all. It’s not as foolproof as the old local Keychain solution, since the user can reject sharing their credentials with Safari, but it’s better than nothing.

Am I missing anything about this?

Out of Context

Twitter’s been having a discussion about tech job interviews recently: you can see my contributions here, here, here, and here.

For the tl;dr crowd, my take is that interviews are extremely difficult, and so you (and I) should have some empathy.

A while ago, I did a bunch of interviews for a junior iOS developer. I gave them what is apparently a quite common exercise: in a simple iOS app, get some JSON data from a server and use it to populate a table, including, for each entry, a link to an image file to download separately.

I just made such a project, called Numbers, available here.

When you first run it, it shows a table filled with entries 1-20, where the brightly-colored number icons are each loaded separately.

A button to the right of the navigation bar is labeled “Wrong”, meaning you’re currently using the image loading implementation that’s incorrect.

If you scroll to the bottom of the table quickly, you’ll see that initially, the rows you uncover will be temporarily populated with incorrect icons:

Screenshot of "Numbers" application table view, scrolled to bottom, with rows 16 through 20 having incorrect icons 2 through 6.

That’s because, in the incorrect implementation, the async network calls insert the loaded images into the cells that originally requested them.

But, as Everyone Knows, cells in UITableView are reused when you scroll, which means by the time the network calls finish, the original cell might be in use for at a different row, and it shouldn’t display the original row’s contents.

Instead, the network call for an image should update the cell that currently represents the row.

If you tap the “Wrong” button, it will change to the text “Right”, and that’s how the application will behave when it reloads the table. Scrolling quickly to the bottom of the table won’t result in erroneously populated images anymore.

When I was interviewing, if the interviewee said they knew table views, I would give them this exercise, and would consider them not worth hiring if they made that rookie mistake.

Nowadays, it’s clear to me that this is a Gotcha! question like any other.

Instead of it being a question about how they think, how they solve problems, it requires a very specific piece of information you either have it your head at that moment, or you don’t.

In a recent interview where I was debugging a problematic table view implementation, I failed to recognize a similar incorrect image loading mechanism — until nudged to do so by the interviewer. I just didn’t see it. If that interviewer had been as quick to judge as I had been in the past, I wouldn’t have gotten the job.

As a final note of curiosity, you might notice that, in the Numbers project, I reset the NSURLSession each time before reloading the table view’s contents. That’s because NSURLSession has its own cache of network call results, and if I didn’t reset it, you would only be able to reproduce the “wrong” behavior the very first time you tried it. Every subsequent time (including across app relaunches), the images would “load” instantaneously from the cache.

While I would never recommend shipping the wrong implementation, even if you did, these days, Apple’s frameworks would mitigate its impact.