18

在 Xcode 7.3 / iOS 9.3 中,Apple从 iOS SDK 中删除了所有私有框架。出于研究目的(不是 App Store!)我需要使用私有框架(即BluetoothManager.framework,但这也是任何其他私有框架的问题)。

因为这些框架不再在 iOS SDK 中提供,所以如果我的项目尝试显式链接到此框架,我会收到构建(链接器)错误。

任何关于长期(呃)解决方案的想法?

4

1 回答 1

26

您可以通过动态链接到私有框架来解决这个问题,而不是在构建时链接更常见的方式。在构建时,BluetoothManager.framework 需要存在于您的开发 Mac 上,以便链接器能够使用它。使用动态链接,您可以将过程推迟到运行时。在设备上,iOS 9.3 仍然存在该框架(当然还有其他框架)。

以下是在Github上修改项目的方法:

1) 在 Xcode 的 Project Navigator 中,在 Frameworks 下,删除对 BluetoothManager.framework 的引用。无论如何,它可能显示为红色(未找到)。

2) 在项目Build Settings下,您将旧的私有框架目录明确列为框架搜索路径。删除它。如果您找不到它,请在构建设置中搜索“PrivateFrameworks”。

3) 确保添加您需要的实际头文件,以便编译器理解这些私有类。例如,我相信您可以在此处获取当前标题。即使从 Mac SDK 中删除了框架,我相信这个人已经在设备上使用了运行时浏览器之类的工具来生成头文件。在您的情况下,将 BluetoothManager.h 和 BluetoothDevice.h 标头添加到 Xcode 项目。

3a)注意:生成的头文件有时无法编译。我必须在上面的运行时浏览器标头中注释掉几个structtypedef,以便构建项目。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"];
于 2016-05-03T09:59:33.520 回答