I'm working on a macOS tool which uses Apple's Safari framework. When running in macOS 10.13, the tool links to and loads it from
/System/Library/PrivateFrameworks/Safari.framework
and all works fine. But when running in macOS 10.12.6, some behaviors are missing. Based on some probing with DTrace, I think that this is because my tool needs to load instead the latest Staged framework, which is here:
/System/Library/StagedFrameworks/Safari/Safari.framework
This is apparently what Safari does, because if I attach to Safari with lldb and run image list
, in 10.13 the list includes only the former path, and in 10.12.6 only the latter.
I tried the following:
NSBundle* stagedBundle = [NSBundle bundleWithPath:@"/System/Library/StagedFrameworks/Safari/Safari.framework"];
That returns nil in 10.13 because there is, at this time, no such directory. However, in 10.12.6, I get a stagedBundle
, and then:
NSBundle* privateBundle = [NSBundle bundleForClass:[BookmarksController class]];
[privateBundle unload];
[stagedBundle load];
The unloading and loading apparently works, because if I log -description
of those two bundles, before running that code the Private bundle is (loaded) and the Staged bundle is (not yet loaded), but after running that code those states are swapped, as desired.
But it is not effective. (1) If I again invoke -bundleForClass:
, passing a class known to be in both frameworks, it gives me the Private bundle. (2) If I invoke -respondsToSelector:
, passing a selector which is known to exist only in the Staged framework, I get NO.
I tried calling _CFBundleFlushBundleCaches()
, as suggested here, but that did not help.
I've also tried changing my target's FRAMEWORK_SEARCH_PATHS
, and installing the Staged framework on my Mac and linking to it, but since this post is already too long I'll just say that this resulted in more heat than light.
How can one selectively load a framework in this situation?
UPDATE
I've tried another approach. After re-reading Apple's Framework Programming Guide, even though it seems really dated, I decided that this framework needs to be weakly linked. Did this:
-load
and -unload
calls-weak_framework Safari
It makes sense to me, builds and runs in both 10.13 and 10.12.6, but it is apparently still loading the undesired Private framework in 10.12.6. NSLog reports that as the bundle's path, and a class does not respond to a selector known to be in Staged framework only.
Any other ideas?
If you're certain that an app you want to install is from a trustworthy source and hasn't been tampered with, you can temporarily override your Mac security settings to open it. Go to Security & Privacy. Click the Open Anyway button in the General pane to confirm your intent to open or install the app.
You may not have the privileges to open the app, the app may be damaged, or it may not be installed. If you aren't an administrator of your Mac, the administrator may be preventing you from using the app.
The . NET (initially called . NET Core) was released in 2016 as the modular, lighter, cross-platform alternative. It therefore works on Windows, Linux, MacOS.
First, a disclaimer: I'd strongly suggest you don't rely on loading private frameworks in any application that you ship to users. It's fragile and unsupported.
That said, if you really want to do this, my suggestion would be to use the same technique that Safari itself uses to select between the two copies of the framework, which is dyld
's DYLD_VERSIONED_FRAMEWORK_PATH
environment variable.
To quote the dyld
man page:
This is a colon separated list of directories that contain potential override frameworks. The dynamic linker searches these directories for frameworks. For each framework found dyld looks at its
LC_ID_DYLIB
and gets thecurrent_version
and install name. Dyld then looks for the framework at the install name path. Whichever has the largercurrent_version
value will be used in the process whenever a framework with that install name is required. This is similar toDYLD_FRAMEWORK_PATH
except instead of always overriding, it only overrides is the supplied framework is newer. Note: dyld does not check the framework's Info.plist to find its version. Dyld only checks the-current_version
number supplied when the framework was created.
In short, this results in dyld
performing a version check between the framework being loaded and the one in the versioned framework path, with the higher version being loaded. If the versioned framework path doesn't exist or the framework in question doesn't exist within it, the original framework path will be used.
Safari makes use of a second dyld
feature to simplify its use of DYLD_VERSIONED_FRAMEWORK_PATH
, the LC_DYLD_ENVIRONMENT
load command. This load command allows DYLD_*
environment variables to be specified at link time that will be applied by dyld
at runtime prior to it attempting to load any dependent libraries. Without this trick you'd need to set DYLD_VERSIONED_FRAMEWORK_PATH
as an environment variable prior to your application being launched, which typically requires a cumbersome re-exec to achieve.
Putting these two building blocks together, you end up adding a configuration setting like:
OTHER_LDFLAGS = -Wl,-dyld_env -Wl,DYLD_VERSIONED_FRAMEWORK_PATH=/System/Library/StagedFrameworks/Safari;
You can then either link statically against /S/L/PrivateFrameworks/Safari.framework
, or attempt to load it dynamically at runtime. Either should result in the appropriate framework being loaded at runtime.
To address some of the misunderstandings your question reveals:
The unloading and loading apparently works, because if I log -description of those two bundles, before running that code the Private bundle is (loaded) and the Staged bundle is (not yet loaded), but after running that code those states are swapped, as desired.
Unloading shared libraries containing Objective-C code isn't supported. I suspect the only thing it does is result in a "loaded" flag being toggled on the NSBundle
instance, since at dyld
's level it is ignored.
In Build Settings > Framework Search Paths, listed paths to both frameworks' parent directories, with the Staged path before the Private path, because I want this one to load in macOS 10.12.6, where both exist.
Framework search paths are a concept that's only used at compile-time. At runtime, the library's install name is what tells dyld
where to find the binary to load.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With