NSPathControl Causing Disk I/O Reading NSURL Resource Values On the Main Thread

Sort of a continuation of - https://developer.apple.com/forums/thread/813641

I've made a great effort to get NSURL -getResourceValue:forKey: calls etc off the main thread. Great progress. So now I'm working with a file on a really slow network volume I discovered a little hang and luckily enough I'm attached to the debugger so I paused that thing. I see where I'm at. It is:

NSPathControl's setURL:. It goes a little something like this:

in realpath$DARWIN_EXTSN ()

+fileSystemRealPath ()

+[FSNode(SandboxChecks) canAccessURL:withAuditToken:operation:] ()

+FSNode(SandboxChecks) canReadFromSandboxWithAuditToken:] ()

LaunchServices::URLPropertyProvider::prepareLocalizedNameValue ()

LaunchServices::URLPropertyProvider::prepareValues () prepareValuesForBitmap ()

FSURLCopyResourcePropertiesForKeysInternal ()

CFURLCopyResourcePropertiesForKeys ()

-[NSURL resourceValuesForKeys:error:] () in function signature specialization <Arg[1] = Dead> of Foundation._NSFileManagerBridge.displayName(atPath: Swift.String) -> Swift.String () in displayName ()

-[NSPathCell _autoUpdateCellContents] ()

-[NSPathCell setURL:] ()

Could maybe, NSPathControl get the display name etc. asynchronously? and maybe just stick raw path components in as a placeholder while it is reading async? Or something like that? If I can preload the resource keys it needs I would but once the NSURL asks on the main main thread I think it will just dump the cache out, per the run loop rules.

FB22294400

Could maybe, NSPathControl get the display name etc. asynchronously? and maybe just stick raw path components in as a placeholder while it is reading async? Or something like that?

Not really. That is, you might be able to subclass the control to get the behavior you want; however, I think the work involved would be fairly similar making your own control using our view components.

However...

If I can preload the resource keys it needs, I would, but once the NSURL asks on the main thread, I think it will just dump the cache out, per the run loop rules.

...you should be able to do this. NSURL does work this way (the run loop reset), but CFURL does NOT, so if you create a CFURLRef and preload it with the expected keys, it should do exactly what you need. Keep in mind that using CFURLRef for this is easier than you think— you don't actually need to convert from NSURL to CFURLRef; you just have to create the CFURLRef and then cast that CFURLRef into an NSURL. What you'll then end up with is an object that works fine as an NSURL but should have different caching behavior (since it's actually a CFURLRef). I'd probably use the CF API to fetch the required resources, but that's more out of an abundance of caution than because it's required.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Not really. That is, you might be able to subclass the control to get the behavior you want; however, I think the work involved would be fairly similar making your own control using our view components.

True. Yea it isn't too much work. Just needed to fetch the titles and the icon images in the background.

but CFURL does NOT,

I briefly thought about this though I wasn't sure how path control grabs the icons (through the url or something else like NSWorkspace) and I wanted to have that in the bg too.

Under normal conditions this isn't a big deal but dealing with a url on a slow network it can cause a little hitch, as I discovered!

I briefly thought about this though I wasn't sure how path control grabs the icons (through the URL or something else like NSWorkspace) and I wanted to have that in the bg too.

Strictly speaking, it uses NSWorkspace.icon(forFile:) but that doesn't really matter at least as far as display works. The rules for EXACTLY which icon will be shown for every possible edge case are sufficiently complex that the only way to ensure consistent behavior is to have a bottleneck through a single API. So NSWorkspace.icon(forFile:) and NSURLEffectiveIconKey are both calling into the same place (currently a private SPI in LaunchServices). I hadn't actually noticed that we'd deprecated kCFURLEffectiveIconKey; however, that doesn't actually change anything about using CFURL. The point of toll-free bridged objects is that the two types should act as full replacements for each other, so you can pass "NSURLEffectiveIconKey"[1] directly into CFURL.

Under normal conditions this isn't a big deal but dealing with a URL on a slow network it can cause a little hitch, as I discovered!

You'll need to test it but I suspect switching to CFURL will at least help, assuming it doesn't solve the problem entirely. Strictly speaking, NSWorkspace.icon(forFile:) means you don't have the benefit of direct caching (like CFURL would); however, I think retrieving the icon once through launch services should be enough to "prime" the system, making the later retrieval faster.

One note on that- the worst case here is a folder full of items which ALL have individually customized icons, so I'd probably make up a test directory of files and then test that over an artificially slowed connection.

[1] If you're wondering why we bother with two different sets, the answer is dependency management. Parts of the system are designated as “low-level" components, which means components like Foundation and the Objective-C/Swift runtime are allowed to use them... which also means that they CAN'T use those components. CoreFoundation exists so that those lower-level components have a "base" set of types they can use instead of Foundation.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

NSPathControl Causing Disk I/O Reading NSURL Resource Values On the Main Thread
 
 
Q