Handling Private Frameworks in Xcode ≥ 7.3

Handling private frameworks in Xcode ≥ 7.3

You can solve this problem by linking to the private framework dynamically, instead of the more common way of linking at build time. At build time, the BluetoothManager.framework would need to exist on your development Mac for the linker to be able to use it. With dynamic linking, you defer the process until runtime. On the device, iOS 9.3 still has that framework present (and the other ones, too, of course).

Here is how you can modify your project on Github:

1) In Xcode's Project Navigator, under the Frameworks, remove the reference to BluetoothManager.framework. It was probably showing in red (not found) anyway.

2) Under the project Build Settings, you have the old private framework directory explicitly listed as a framework search path. Remove that. Search for "PrivateFrameworks" in the build settings if you have trouble finding it.

3) Make sure to add the actual headers you need, so the compiler understands these private classes. I believe you can get current headers here for example. Even if the frameworks are removed from the Mac SDKs, I believe this person has used a tool like Runtime Browser on the device to generate the header files. In your case, add BluetoothManager.h and BluetoothDevice.h headers to the Xcode project.

3a) Note: the generated headers sometimes don't compile. I had to comment out a couple struct typedefs in the above Runtime Browser headers in order to get the project to build. Hattip @Alan_s below.

4) Change your imports from:

#import <BluetoothManager/BluetoothManager.h>

to

#import "BluetoothManager.h"

5) Where you use the private class, you're going to need to first open up the framework dynamically. To do this, use (in MDBluetoothManager.m):

#import <dlfcn.h>

static void *libHandle;

// A CONVENIENCE FUNCTION FOR INSTANTIATING THIS CLASS DYNAMICALLY
+ (BluetoothManager*) bluetoothManagerSharedInstance {
Class bm = NSClassFromString(@"BluetoothManager");
return [bm sharedInstance];
}

+ (MDBluetoothManager*)sharedInstance
{
static MDBluetoothManager* bluetoothManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// ADDED CODE BELOW
libHandle = dlopen("/System/Library/PrivateFrameworks/BluetoothManager.framework/BluetoothManager", RTLD_NOW);
BluetoothManager* bm = [MDBluetoothManager bluetoothManagerSharedInstance];
// ADDED CODE ABOVE
bluetoothManager = [[MDBluetoothManager alloc] init];
});
return bluetoothManager;
}

I placed the call to dlopen in your singleton method, but you could put it elsewhere. It just needs to be called before any code uses the private API classes.

I added a convenience method [MDBluetoothManager bluetoothManagerSharedInstance] because you'll be calling that repeatedly. I'm sure you could find alternate implementations, of course. The important detail is that this new method dynamically instantiates the private class using NSClassFromString().

6) Everywhere you were directly calling [BluetoothManager sharedInstance], replace it with the new [MDBluetoothManager bluetoothManagerSharedInstance] call.

I tested this with Xcode 7.3 / iOS 9.3 SDK and your project runs fine on my iPhone.

Update

Since there seems to be some confusion, this same technique (and exact code) still works in iOS 10.0-11.1 (as of this writing).

Also, another option to force loading of a framework is to use [NSBundle bundleWithPath:] instead of dlopen(). Notice the slight difference in paths, though:

handle = dlopen("/System/Library/PrivateFrameworks/BluetoothManager.framework/BluetoothManager", RTLD_NOW);
NSBundle *bt = [NSBundle bundleWithPath: @"/System/Library/PrivateFrameworks/BluetoothManager.framework"];

Xcode 7.3 missing Private Frameworks

According to the Xcode 7.3 release notes:

The Apple private frameworks have been removed from the iOS, watchOS, and tvOS SDKs. If your application fails to link, make sure that you are not using any private frameworks. The use of private frameworks is an unsupported configuration and applications that use non-public APIs will be rejected by the App Store - see App Store Guideline 2.5. (22330301)

You have two options:

  1. Remove your dependency on these private frameworks.
  2. Copy the private frameworks from a previous version of Xcode and link with them. There is no guarantee they will continue to work on devices running iOS 9.3 and above.

Are private frameworks supported on iOS?

It appears that custom frameworks are now supported by Xcode 6:

iOS developers can now create dynamic frameworks. Frameworks are a
collection of code and resources to encapsulate functionality that is
valuable across multiple projects. Frameworks work perfectly with
extensions, sharing logic that can be used by both the main
application, and the bundled extensions.

iPhone private API compiling


Background

In order to use methods in any framework, you can choose to either reference those frameworks statically or dynamically. I haven't seen anything in your question that suggests you need to use dynamic linking, so I'm going to avoid that (it's slightly more complicated for a beginner). (‡)

To statically reference APIs in a framework, you would import the relevant headers, and then configure your Xcode project to link to the framework. These two steps only change slightly for Private APIs.

Private APIs usually don't provide you with the headers (*.h) that describe the APIs. I say "usually", because sometimes, an API that's private on iOS is actually public on Mac OS X, so to use it, you simply copy the OS X version of the header into your project.

Generating Headers

Probably more common, though, is that you have to generate the header yourself. If you know which header you need, often you can find it posted online under someone's github account. If not, you need a tool like class-dump, or class-dump-z. Run the class dump tool on the private framework, by finding it on your Mac:

cd /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.0.sdk/System/Library/PrivateFrameworks/
class-dump -H -o ~/Headers/7.0/MusicLibrary/ MusicLibrary

Then, go into ~/Headers/7.0/MusicLibrary/ and find lots of dumped header files. Copy (only) the header(s) you need into your Xcode iOS project directory. Then, from inside Xcode, right click on a source folder in your Project Navigator view, select "Add files to <Project Name> ...". Pick the dumped header file you need to include in your project.

Linking

In order to successfully link against the API, you also need to add the framework to your Xcode Build Phases. From your project Target settings, select Build Phases then Link Binary with Libraries. You normally choose a public framework from the default list that the iOS SDK provides you. However, you can choose to browse your Mac for 3rd-party frameworks, or private frameworks, too. For private frameworks, you're just going to have to navigate to a folder location like this

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.0.sdk/System/Library/PrivateFrameworks/

and then pick the *.framework directory.

Then, simply use the APIs like you would use any public/private API. #import the header file, call the APIs, instantiate the classes, etc.

The use of this code:

char *framework = "/System/Library/PrivateFrameworks/...";
dlopen(...);

is an attempt to dynamically open a private framework. That's not necessary, if you know at compile time which framework you want to use, and have it present on your Mac to let Xcode link against.

Entitlements

Entitlements are not new to iOS 7. They have existed for quite some time, and are one technique iOS uses to prevent unauthorized usage of some private APIs. iOS will check to see if your app has been granted a particular entitlement (by name), and if it does not have that entitlement, calling the protected API will fail (usually silently, although sometimes you'll see a message in the Console log).

See here for an example of granting your (jailbreak) app an entitlement.


(‡) Update: iOS 9.3 has brought some changes with respect to Private APIs, and static vs dynamic linking. Please see this Stack Overflow question here for more.

Using private frameworks for QT in xcode 4.3

Some options to deploy a Qt application on Mac:

  • Qt comes with the macdeployqt tool, to bundle Qt with the built app bundle. The tool is a bit limited though and might or might not do what you want.
  • CMake comes with DeployQt4, which can also be used for deployment only, in case one doesn't want to use cmake for the build as well.
  • One can write his own script using install_name_tool as described in your link. If it doesn't work, check with otool -L if you replaced all absolute paths in the executable and bundled libraries. You will have to fix the paths for the bundled libraries, too! (recursively, so to say).

The latest released cmake version (2.8.7) doesn't support XCode 4.3 properly though: Since Xcode 4.3, files previously being installed to /Developer are now in /Applications/Xcode.app/Contents/Developer. cmake still expects them in /Developer. That is fixed in cmake master but not released yet. A workaround is to create a symlink:

ln -s /Applications/Xcode.app/Contents/Developer /Developer


Related Topics



Leave a reply



Submit