Determine what exactly causes a certain system library to be loaded

I'm in the process of refactoring a humongous monolithic app with multiple extensions into a setup where we have a service library which is linked to each of the targets.

However, this causes a significant issue when the service library is being referenced from the notification extension, as that extension has a hard memory limit of 12MB on an iPhone 5s, which we are still supporting, and our memory usage is at about 11MB which is way too close for comfort. When the memory usage surpasses the limit, the notification extension is automatically killed by the system.

Profiling with Instruments/Allocations has shown that a significant chunk of that memory is allocated (wasted, really) by the internal AVSecondScreenController.load() class initializer from the AVKit library.

My approach is now to try to figure out what causes AVKit to be loaded in the first palce. I have no explicit imports for AVKit in the notification extension and attempts to figure out what triggers it from the service library failed as well.

I did add a symbolic breakpoint for AVSecondScreenController.load() which is hit, but the backtrace is useless.

Does anyone have any idea how I can pinpoint what causes AVKit to be loaded in the first place?

Thanks!

Replies

The general advice I like to give in these extension situations is to carefully architect your code to have separate UI and non-UI components so that you don't pay the cost of loading the UI frameworks into a non-UI extension point. This is what we do throughout the SDK, such as Intents and IntentsUI, CoreLocation and CoreLocationUI, etc. Getting to that ideal during a refactor isn't always easy, but I hope it sets a goal for you to aspire to so that you'll be in a better place long-term.

As to debugging your current situation, you can try setting a symbolic breakpoint on +[AVSecondScreenController load], though I wouldn't be surprised if this is just run via dyld as part of loading the extension process. You can also look at a link map (set LD_GENERATE_MAP_FILE to Yes in build settings and look for the output at the path in LD_MAP_FILE_PATH).

The toughest situation will be if this is a transitive dependency, meaning it is included because of another system framework. If that's the case, at least the link map info will help you inventory everything you're pulling in, and you can start looking to cut down that list and eventually get to your goal that way.

Thank you for the response edford!

Indeed, I am aware that the monolith approach we started on many years ago was wrong and it's clear we need to further break down our dependencies after de-spaghettifying our code.

Unfortunately the map file didn't help so first I tried to identify and remove anything that belongs to AVKit (AVPlayerViewController, AVPictureInPictureController) but I'd still end up with +[AVSecondScreenController load] being called at one point so the issue was indeed an indirect dependency.

I replicated our dependency graph in an empty project and then started experimenting with it, removing stuff that made might have been connected to AVKit, until eventually I found out what the "problem" was, which was QLPreviewController- whenever I had a reference to its initializer anywhere, this would trigger a load of AVKit and the call to the second screen class initializer.

Considering my service library which is included from the main app and the extension targets, is there a way to selectively decide at runtime whether to load a framework or not? There isn't one as far as I know.

Thanks!

until eventually I found out what the "problem" was, which was QLPreviewController- whenever I had a reference to its initializer anywhere, this would trigger a load of AVKit and the call to the second screen class initializer.

In a way, this makes sense, as QLPreviewController needs to be able to preview common file formats, including media files. I'm glad you found the reason!

Considering my service library which is included from the main app and the extension targets, is there a way to selectively decide at runtime whether to load a framework or not? There isn't one as far as I know.

The only way you could do this is with dlopen, and that's not an approach we recommend; in general, using dlopen over the system's default loading at process load throws away app launch performance benefits. More specific to this situation, you can't control if another system framework will require a secondary system framework to load, which would go around any use of dlopen you have as an attempt to control this situation.