With Xcode 7.3 / iOS 9.3 Apple removed all private frameworks。出于研究目的(不是App Store!),我需要使用私有框架(即BluetoothManager.framework
,但这也是任何其他私有框架的问题)。
由于iOS SDK中不再提供这些框架,如果我的项目尝试显式链接到此框架,则会出现构建(链接器)错误。
任何关于长期(呃)期限解决方案的想法?
答案 0 :(得分:22)
您可以通过链接到私有框架动态来解决此问题,而不是在构建时链接的更常见方式。在构建时,BluetoothManager.framework需要存在于您的开发Mac上,以便链接器能够使用它。通过动态链接,您可以将流程推迟到运行时。在设备上,iOS 9.3仍然存在该框架(当然也包括其他框架)。
以下是您在Github上修改项目的方法:
1)在Xcode的Project Navigator中,在Frameworks下,删除对BluetoothManager.framework的引用。无论如何,它可能显示为红色(未找到)。
2)在项目构建设置下,您将旧的私有框架目录明确列为框架搜索路径。删除它。如果您在查找时遇到问题,请在构建设置中搜索“PrivateFrameworks”。
3)确保添加所需的实际标头,以便编译器理解这些私有类。我相信你可以获得当前的标题here for example。即使从Mac SDK中删除了框架,我相信此人已在设备上使用Runtime Browser之类的工具来生成头文件。在您的情况下,将BluetoothManager.h和BluetoothDevice.h标头添加到Xcode项目中。
3a)注意:生成的标题有时无法编译。我必须在上面的Runtime Browser headers中注释掉几个struct
typedef才能生成项目。 Hattip @Alan_s下面。
4)更改您的导入:
#import <BluetoothManager/BluetoothManager.h>
到
#import "BluetoothManager.h"
5)在使用私有类的地方,您需要首先动态地打开框架。为此,请使用(在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;
}
我在你的单身方法中调用了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"];