Lifecycle of a tvOS 13.2 TopShelf extension?

So recently I migrated the topshelf extension for my app from the deprecated TVServiceProvider to the new TVContentProvider in 13.0 and onwards.


I finally got it working (not helped by wasting hours figuring out that the NSExtensionPrincipalClass has to be the first thing listed in the NSExtension dictionary in the Info.plist or the extension just terminates, I kid you not!) but there is one last thing that I can't figure out.


What works:


1. remove any instance of my application from the Apple TV

2. install and launch my app from xcode on the Apple TV

3. when i back out of the app, the topshelf code is working, it calls the loadTopShelfContentWithCompletionHandler function and I am able to give it what it wants and it gets displayed correctly


Problem is, if I terminate the application from Xcode, I can no longer get the topshelf to work when I try and launch it again from Xcode. It is not listed as running (as a process to attach to, for example). The app runs fine, but there is no extension process launched alongside it.


I can get it working again in one of two ways; either A) reboot the Apple TV, in which case I find tvOS launches the extension a few seconds after boot without me doing anything (not even highlighting my app), or, B) following the steps above if I delete the instance from the Apple TV and install it again using Xcode.


Essentially, it behaves as if the top shelf extension is launched once, and only once, on bootup of the Apple TV, or on first install. It appears to get terminated when I launch the app again using xcode (e.g. with a new build or something, or even running the same build) and only A) or B) above can get it running again.


Has anyone else seen this?

Just to add, I get the same behaviour from the Apple example project:


https://developer.apple.com/documentation/tvservices/building_a_full_screen_top_shelf_extension?language=objc


I also forgot to mention this is with Xcode 11.2.1.

I also have the same problem 😟 Any solution?

In tvOS 14.+ I experienced a similarly undesired behavior. When I launched the TopShelf extension in the tvOS Simulator, using TopShelf as the Run Target, everything went fine all the time.

When TestFlight users installed the app with the newly included TopShelf extension, the TopShelf did not load. Only after a power cycle, the TopShelf Items showed up.

How is that feature supposed to work for apps which provide a TopShelf extension only as a later addition? Will the users honestly have to pull the power plug before they can see it?
Same here. When I debug the Extension with TopShelf as the Run Target, I see the top shelf, but cannot "wait for app to launch" and attach to debug the cold start scenario. When I chose to run the app without building, it is build nevertheless and the top shelf extension is not loading anymore.

Late to this thread, but it still happens on the tvOS 27.0 public beta (24J5325d), on an Apple TV 4K (3rd gen), with an app whose deployment target is tvOS 17. I don't think the caching angle has come up here.

Something a reboot does that reinstalling doesn't: the Home screen process caches the rendered Top Shelf tile and dedupes incoming content against that cache. In my case that cache survived a changed item identifier and a full uninstall and reinstall of the app, and kept drawing a tile built from an earlier build for hours while the current code was correct.

The log line that finally showed it:

Skipping content update for [com.example.app] because it is unchanged

If your item identifier is a constant, a corrected item is indistinguishable from the cached one, so a fix to what's in the tile can never reach the screen. Deriving the identifier from the content you're rendering closes that particular trap. (Use the content string itself, not its hashValue — Swift seeds Hashable per process, so a hash changes every launch regardless of content, which isn't what you want.)

I want to be careful not to overclaim: your symptom is the extension not running at all after an Xcode relaunch, and mine was a running extension whose output wasn't reaching the screen. Those may well be different bugs. But if you're in the position of "only a reboot fixes it," it's worth checking whether the system is even accepting your content before concluding the extension is dead.

Two other things that cost me an evening and are much easier to state than to discover:

  • print() from a Top Shelf extension reaches nobody, and log stream has no device flag. os.Logger at .notice, read in Console.app with the Apple TV tethered, is the channel that works. Not .debug, which isn't persisted by default and so is missing from exactly the capture you collected.

  • An extension can read the shared App Group container but is sandbox-denied from creating files in it — deny(1) file-write-create, surfacing as NSCocoaErrorDomain 513. The containing app has no such restriction. If you need something dynamic on the shelf, the app writes it and the extension only reads it.

Full write-up if it's any use: https://mcmizzle.com/blog/tvos-top-shelf-stale-cache/

Lifecycle of a tvOS 13.2 TopShelf extension?
 
 
Q