Handling private frameworks in Xcode ≥ 7.3

流过昼夜 提交于 2019-12-27 19:15:07

问题


With Xcode 7.3 / iOS 9.3 Apple removed all private frameworks from the iOS SDKs. For research purposes (not App Store!) I need to work with a private framework (namely BluetoothManager.framework, but this is also an issue for any other private frameworks).

Because these frameworks are no longer delivered in the iOS SDKs, I get a build (linker) error if my project attempts to link to this framework explicitly.

Any ideas for a long(er)-term solution?


回答1:


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"];


来源:https://stackoverflow.com/questions/37000053/handling-private-frameworks-in-xcode-%e2%89%a5-7-3

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!