您可以通过动态链接到私有框架来解决此问题,而不是常见的构建时链接方式。在构建时,链接器需要在开发 Mac 上存在 BluetoothManager.framework 才能使用它。使用动态链接,您可以将此过程推迟到运行时。在设备上,iOS 9.3 仍然具有该框架(当然还有其他框架)。
以下是您可以在
Github 上修改项目的方法:
1)在 Xcode 的项目导航器中,在 Frameworks 下,删除对 BluetoothManager.framework 的引用。它可能已经以红色显示(未找到)。
2)在项目的 Build Settings 下,您将旧的私有框架目录明确列为框架搜索路径。删除它。如果您无法找到它,请在构建设置中搜索“PrivateFrameworks”。
3) 确保添加所需的实际头文件,以便编译器理解这些私有类。我相信你可以在这里找到当前的头文件。即使 Mac SDK 中的框架被移除,我认为此人已经在设备上使用了类似 Runtime Browser 的工具来生成头文件。在你的情况下,请将 BluetoothManager.h 和 BluetoothDevice.h 头文件添加到 Xcode 项目中。
3a) 注意:生成的头文件有时无法编译。我不得不注释掉上面的 Runtime Browser headers 中的一些 struct
typedefs 才能使项目构建成功。感谢下面的 @Alan_s。
4) 将你的导入从:
到
5) 当您使用私有类时,您需要首先动态打开框架。为此,请在MDBluetoothManager.m中使用以下代码:
#import <dlfcn.h>
static void *libHandle;
+ (BluetoothManager*) bluetoothManagerSharedInstance {
Class bm = NSClassFromString(@"BluetoothManager");
return [bm sharedInstance];
}
+ (MDBluetoothManager*)sharedInstance
{
static MDBluetoothManager* bluetoothManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
libHandle = dlopen("/System/Library/PrivateFrameworks/BluetoothManager.framework/BluetoothManager", RTLD_NOW);
BluetoothManager* bm = [MDBluetoothManager bluetoothManagerSharedInstance];
bluetoothManager = [[MDBluetoothManager alloc] init];
});
return bluetoothManager;
}
我在单例方法中调用了
dlopen
,但你可以把它放在其他地方。它只需要在任何代码使用私有API类之前被调用
即可。
我添加了一个方便的方法
[MDBluetoothManager bluetoothManagerSharedInstance]
,因为你将会多次调用它。当然,你也可以找到替代实现。关键细节在于这个新方法使用
NSClassFromString()
动态实例化私有类。
6) 无论你在哪里直接调用
[BluetoothManager sharedInstance]
,都将其替换为新的
[MDBluetoothManager bluetoothManagerSharedInstance]
调用。
我用Xcode 7.3/iOS 9.3 SDK测试了这个项目,在我的iPhone上运行良好。
更新
由于似乎存在一些混淆,(截至本文)这个技术和完整代码仍可在iOS 10.0-11.1中使用。
此外,强制加载框架的另一种选择是使用
[NSBundle bundleWithPath:]
而不是
dlopen()
。不过,请注意路径的轻微差异:
handle = dlopen("/System/Library/PrivateFrameworks/BluetoothManager.framework/BluetoothManager", RTLD_NOW)
NSBundle *bt = [NSBundle bundleWithPath: @"/System/Library/PrivateFrameworks/BluetoothManager.framework"]